页与记录

Ⅰ、无主键的一个小测试

1.1 表上存在唯一键

(root@localhost) [test]> show create table test_key\G
*************************** 1. row ***************************
       Table: test_key
Create Table: CREATE TABLE `test_key` (
  `a` int(11) DEFAULT NULL,
  `b` int(11) NOT NULL,
  `c` int(11) NOT NULL,
  UNIQUE KEY `b` (`b`),
  UNIQUE KEY `c` (`c`),
  UNIQUE KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

(root@localhost) [test]> select *, _rowid from test_key;
+------+---+---+--------+
| a    | b | c | _rowid |
+------+---+---+--------+
|    1 | 2 | 3 |      2 |
|    4 | 5 | 6 |      5 |
|    7 | 8 | 9 |      8 |
+------+---+---+--------+
3 rows in set (0.00 sec)

可以看出,b列被作为了主键

(root@localhost) [test]> show create table test_key2\G
*************************** 1. row ***************************
       Table: test_key2
Create Table: CREATE TABLE `test_key2` (
  `a` varchar(4) DEFAULT NULL,
  `b` varchar(4) NOT NULL,
  `c` varchar(4) NOT NULL,
  UNIQUE KEY `b` (`b`),
  UNIQUE KEY `c` (`c`),
  UNIQUE KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

(root@localhost) [test]> select *, _rowid from test_key2;
ERROR 1054 (42S22): Unknown column '_rowid' in 'field list'
这里_rowid只有当key类型为id时才有效

换俩办法看即可
法1:
(root@localhost) [test]> select * from information_schema.columns where table_name='test_key2' and column_key='pri'\G
*************************** 1. row ***************************
           TABLE_CATALOG: def
            TABLE_SCHEMA: test
              TABLE_NAME: test_key2
             COLUMN_NAME: b
        ORDINAL_POSITION: 2
          COLUMN_DEFAULT: NULL
             IS_NULLABLE: NO
               DATA_TYPE: varchar
CHARACTER_MAXIMUM_LENGTH: 4
  CHARACTER_OCTET_LENGTH: 4
       NUMERIC_PRECISION: NULL
           NUMERIC_SCALE: NULL
      DATETIME_PRECISION: NULL
      CHARACTER_SET_NAME: latin1
          COLLATION_NAME: latin1_swedish_ci
             COLUMN_TYPE: varchar(4)
              COLUMN_KEY: PRI
                   EXTRA:
              PRIVILEGES: select,insert,update,references
          COLUMN_COMMENT:
   GENERATION_EXPRESSION:
1 row in set (0.00 sec)

法2:
(root@localhost) [test]> desc test_key2;
+-------+------------+------+-----+---------+-------+
| Field | Type       | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| a     | varchar(4) | YES  | UNI | NULL    |       |
| b     | varchar(4) | NO   | PRI | NULL    |       |
| c     | varchar(4) | NO   | UNI | NULL    |       |
+-------+------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
看到b列被作为主键

1.2 表上无唯一键

当表中未显式指定主键,且没有非空唯一键时,系统会自定义一个主键(6个字节,int型,全局,隐藏)

(root@localhost) [test]> show create table test_key3\G
*************************** 1. row ***************************
       Table: test_key3
Create Table: CREATE TABLE `test_key3` (
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  `c` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

(root@localhost) [test]> select * from test_key3;
+------+------+------+
| a    | b    | c    |
+------+------+------+
|    1 |    2 |    3 |
|    4 |    5 |    6 |
|    7 |    8 |    9 |
+------+------+------+
3 rows in set (0.00 sec)

(root@localhost) [test]> select *, _rowid from test_key3;
ERROR 1054 (42S22): Unknown column '_rowid' in 'field list'
(root@localhost) [test]> select * from information_schema.columns where table_name='test_key3' and column_key='pri'\G
Empty set (0.00 sec)

(root@localhost) [test]> desc test_key3;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| a     | int(11) | YES  |     | NULL    |       |
| b     | int(11) | YES  |     | NULL    |       |
| c     | int(11) | YES  |     | NULL    |       |
+-------+---------+------+-----+---------+-------+
3 rows in set (0.00 sec)
由上可见,这种情况下_rowid我们是看不了的,对我们透明

tips:

假设有两张表都使用了系统定义的主键,则系统定义的主键的id并不是表内单调递增的,而是全局递增

该系统的rowid是定义在ibdata1.ibd中的sys_rowid中,全局自增

6个字节表示的数据量为 2^48 ,通常意义上是够用的

强烈建议必须自己显示定义主键

Ⅱ、页的结构

One Page
File Header
Page Header
Infimun + Supermum Records
User Records
Free Space
Page Directory
File Trailer
  • 文件头、页头、文件结尾信息三部分大小固定分别为,38,56,8字节,这些信息用来记录page的一些信息,如其所在b+ tree的位置,checksum等
  • 用户记录,空闲空间,页目录为实际的行记录存储空间,大小不固定
  • Infimun + Supermum Records记录两条虚拟记录,一个最小,一个最大
  • 一个page最多存放16k/2 - 200行记录,即7992行

2.1 File Header

- 字节数 备注
FIL_PAGE_SPACE_OR_CHKSUM 4 4.0.14之前为0,现在为page的checksum值
Fil_PAGE_OFFSET 4 page的偏移位置,用于定位页在一个表空间中的位置
FIL_PAGE_PREV 4 上一个
FIL_PAGE_NEXT 4 下一个页
FIL_PAGE_LSN 8 该页ui后一次被修改的LSN
FIL_PAGE_TYPE 2 页的类型(0x45BF表示存放的是数据页)
FIL_PAGE_FILE_FLUSH_LSN 8 仅存在系统表空间的一个页中定义,表示文件至少被更新到了该LSN,独立表空间中该值为0
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4 4.1开始该值表示页属于哪个空间

InnoDB中页的类型:

B+树页,Undo Log页,索引节点,系统页,最新分配页,BLOB页,Insert Buffer空闲列表与位图等等

2.2 Page Header

记录数据页的状态信息,共由14个部分组成,占56个字节,此处不展开分析

2.3 Infimum和Supermum Record

表示两个虚拟记录,用于限定页的边界,页创建时即被创建,永远不被删除,不同行格式下,大小不一样

2.4 User Record 和 Free Space

User Record为实际存储记录的内容,Innodb中总是B+ tree索引组织

Free Space为空闲空间,是一个链表结构,一条记录被删,该空间被加入到空闲链表中

2.5 Page Directory

存放记录的相对位置(page的相对位置)

查一条记录不能直接查到,只能定位到此记录所在的page,将page拽到内存中,通过Page Directory进行二叉查找

2.6 File Trailer

用于检测page是否正常落盘

内部是一个FIL_PAGE_END_LSN,占用8字节,前4字节代表checksum,后4字节与File Header中FIL_PAGE_LSN相同,将这两个值和File_Header中FIL_PAGE_SPACE_OR_CHECKSUM、FIL_PAGE_LSN对比来保证page的完整性(not corrupted),对比的时候需要通过innodb的checksum函数来做,不是等值对比

tips:

每次从磁盘读取一个page检测完整性会带来一定的开销

5.6.6开始innodb_checksum_algorithm参数可控制checksum的算法,默认crc32,之前是innodb,前者更高效

若以strict算法来保存页,低版本MySQL将无法读取这些页,需要mysql_upgrade升级

strict_crc32是最快的校验方式,推荐

Ⅲ、记录

3.1 记录的格式——ROW_FORMAT

格式 说明
REDUDENT 4.1之前默认格式
COMPACT 5.6默认格式
COMPRESSED 支持压缩
DYNAMIC 5.7默认格式,优化了大对象

3.2 COMPACT结构

variable string length list NULL flag record header col col2 ...
列长度小于255字节,用1字节表示,否则2字节(逆序) 1字节 5字节

tips:

  • 记录头信息保存该行是否被删除等信息
  • NULL不占实际存储
  • 每行数据除了自定义列,还有两个隐藏列,事务id(6字节)和回滚指针(7字节),若无自定义主键会增加一个6字节的rowid列

3.2 DYNAMIC

DYNAMIC相比COMPACT,优化了大对象记录的存储

假设一条记录A B C D四列,i中D列是text类型,含有2w字节的长度

COMPACT会存储text中前768字节,剩余数据通过20字节的指针指向溢出页

DYNAMIC存储如下,no prefix data

DYNAMIC在一个页中存储的记录数更多(768字节的prefix,一调记录的字节假设是800,那16k的页只能存放20条记录),这样b+tree就可能变高,读取io次数变多,性能变差

3.3 记录的更新

原地更新(in place update)

  • 原地更新不会占用新的存储空间
  • 非原地更新需要删除(物理删除)原来的空间,然后更新后的数据插入到页的后面
  • 删除的数据空间,会插入到Free_List链表头部
  • 原地更新不会触发页的分裂

Free_List是将页中被删除的空间串联在一起(组成一个链表),当有数据被插到页内时,先看一下Free_list中第一个空间的大小,如果空间合适 ,就将该记录插入到第一个空间中去,如果不合适 ,直接插入到页的尾部的剩余空间(不会去看Free_list的第二个空间)

当该页的数据被插满了,不会马上进行分页,而是进行 reorganize 操作,即将页内的数据在内存中进行整理,然后覆盖原来的页(不影响性能),所以InnoDB不需要碎片整理

Reorganize

posted @ 2018-05-05 00:49  91洲际哥  阅读(386)  评论(0编辑  收藏  举报