InnoDB表结构 未明确指定主键时:首先判断是否有非空的唯一索引(UNIQUE NOT NULL),若有则将其默认为主键,否则创建一个6字节的ROWID。
4.1 逻辑存储结构:tablespace->segment->extent->page

- 表空间是只增不减的,即使ROLLBACK后也并不收缩,而是标记为可用空间,供下次undo使用。
- 数据段即B+树的页节点(leaf node segment),索引段即为B+树的非索引节点(non-leaf node segment)。
- InnoDB存储引擎对于段的管理是由引擎本身完成的。表空间是由分散的页和段组成。
- 区由64个连续的页组成,每个页大小16KB,即每个区1MB。创建新表时,先有32页大小的碎片页(fragment page)存放数据,使用完后才是区(64个连续页)的申请。(InnoDB最多每次申请4个区,保证数据的顺序性能)
- 页类
型有:数据页(B-tree Node)、UNDO页(Undo Log Page)、系统页(System
Page)、事务数据页(Transaction System Page)、插入缓冲位图页(Insert Buffer
Bitmap)及插入缓冲空闲列表页(Insert Buffer Free List)等。
4.2 InnoDB行记录格式 1.Compact行格式
变长字段长度列表(1个或2个字节)
|
NULL标志位
|
记录头信息(5个字节)
|
列1数据
|
列2数据
|
……
|
注:1.书中指明变长字段长度列表最多占2个字节是MySQL中varchar最大长度不超过65535的原因,指的是varchar列总长度应不超过65535,且需要考虑字符集因素(后文也提到了)。
2.NULL标志位的长度(n_nullable+7)/8
bytes。(书中似乎排版有些问题) 表1 Compact行记录头信息
名称
|
大小(bit)
|
描述
|
()
|
1
|
未知
|
()
|
1
|
未知
|
Deleted_flag
|
1
|
该行是否已被删除
|
Min_rec_flag
|
1
|
为1,如果该记录是预先被定义为最小的记录
|
N_owned
|
4
|
该记录拥有的记录数(the number of records owned by this record,没能搞明白到底指的啥意思)
|
Heap_no
|
13
|
索引堆中该条记录的排序记录
|
Record_type
|
3
|
记录类型000=普通 001=B+树节点指针 010=infimum 011=supremum 1xx=保留
|
Next_recorder
|
16
|
页中下一条记录的相对位置
|
2.Redundant行格式
字段长度偏移列表(1个或2个字节)
|
记录头信息(6个字节)
|
列1数据
|
列2数据
|
……
|
表2 Redundant行记录头信息
名称
|
大小(bit)
|
描述
|
()
|
1
|
未知
|
()
|
1
|
未知
|
Deleted_flag
|
1
|
该行是否已被删除
|
Min_rec_flag
|
1
|
为1,如果该记录是预先被定义为最小的记录
|
N_owned
|
4
|
该记录拥有的记录数(the number of records owned by this record)
|
Heap_no
|
13
|
索引堆中该条记录的排序记录
|
N_fields
|
10
|
记录中列的数量(决定了表的列数最多为1023)
|
1byte_offs_flag
|
1
|
偏移列表为1个字节还是2个字节
|
Next_recorder
|
16
|
页中下一条记录的相对位置
|
不管Compact或者Redundant,对于varchar中的NULL在列记录中只占NULL标志位,而不占实际存储空间;但是对于char中的NULL,两者的处理则是不同的:Compact格式下,char中NULL同样不占存储空间,而Redundant下则是占存储空间的。
同时,行记录中还包括两个隐藏列:事务ID列(6字节)和回滚指针列(7字节);若没有定义的Primary Key时,还会增加一个6字节的RowID列。
InnoDB在页内部是通过一种链表方式串联各个行记录的。
InnoDB可以将一条记录中的某些数据存储在真正的数据页面之外,即作为行溢出数据,并将行溢出内容放入Uncompress BLOB Page。此做法保证了每页上至少有两行记录(否则失去了B+树的意义,变成链表了)
3.Compressed与Dynamic行记录格式
InnoDB Plugin引入了新的文件格式Barracuda,对于存放BLOB的数据采用了完全的行溢出方式,在数据页只存储20个字节的指针,实际数据均存放在BLOB Page中。而之前的Compact和Redundant会存放768个前缀字段在数据页。Barracuda中两种行记录格式:Compressed和Dynamic。
InnoDB通过Named File Formats机制来解决不同版本页结构兼容性的问题。不同页格式之间的关系如下: 
在多字节字符集的情况下,CHAR和VARCHAR的行存储(所需存储空间)基本是没有区别的,均是不定长。
4.3 InnoDB数据页(B-tree node)结构
InnoDB数据页由七部分组成:
- File Header:文件头( 38 bytes )
- Page Header:页头( 56 bytes )
- Infimum + Supremum Records:页中上/下界记录
- Users Records:用户记录,即行记录
- Free Space:空闲空间
- Page Directory:也目录
- File Trailer:文件结尾信息
表3 File Header(共38 bytes)
名称
|
大小(byte)
|
存储信息
|
FIL_PAGE_SPACE_OR_CHKSUM
|
4
|
页的CHECKSUM值
|
FIL_PAGE_OFFSET
|
4
|
表空间中页的偏移量
|
FIL_PAGE_PREV
|
4
|
上一页/下一页(B+ Tree决定了叶子节点为双向列表)
|
FIL_PAGE_NEXT
|
4
|
FIL_PAGE_LSN
|
8
|
该页最后被修改的日志序列位置LSN
|
FIL_PAGE_TYPE
|
2
|
页类型
|
FIL_PAGE_FILE_FLUSH_LSN
|
8
|
"the file has been flushed to disk
at least up to this lsn" (log serial number),仅在文件第一页有效
|
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID
|
4
|
属于哪个表空间
|
表4 Page Header(共56 bytes)
名称
|
大小(byte)
|
存储信息
|
PAGE_N_DIR_SLOTS
|
2
|
页目录中slot(槽)数
|
PAGE_HEAP_TOP
|
2
|
堆中第一个记录的指针
|
PAGE_N_HEAP
|
2
|
堆中的记录数,最高bit位指明了行格式,0-Redundant,1-compact
|
PAGE_FREE
|
2
|
指向空闲列表的指针
|
PAGE_GARBAGE
|
2
|
已删除记录(delete flag为1)的字节数
|
PAGE_LAST_INSERT
|
2
|
指向最后插入记录的指针
|
PAGE_DIRECTION
|
2
|
最后插入方向。(会影响InnoDB效能)
|
PAGE_N_DIRECTION
|
2
|
一个方向上连续插入记录的数目
|
PAGE_N_RECS
|
2
|
该页中记录的数目
|
PAGE_MAX_TRX_ID
|
8
|
修改当前页的最大事务ID(仅在Secondary Index)
|
PAGE_LEVEL
|
2
|
当前页在索引树中的位置(0x00代表叶节点)
|
PAGE_INDEX_ID
|
8
|
当前页属于哪个索引ID
|
PAGE_BTR_SEG_LEAF
|
10
|
B+树叶节点中文件段的首指针位置
|
PAGE_BTR_SEG_TOP
|
10
|
B+树非叶节点中文件段的首指针位置
|
Page Directory:存放记录的相对位置
InnoDB是一个稀疏目录(sparse
directory),即一个槽(slot)可能属于多个记录,最少属于4条记录,最多属于8条记录。
Slot中记录按键顺序存放,以便可利用二叉查找迅速招到记录的指针。二叉查找的结果也只是一个粗略的结果,InnoDB必须通过recorder header中的next_record来继续查找相关记录。同时slot也解释了recorder header中n_owned值的含义,即还有多少记录需要查找,因为这些记录并不包括在slots中。
B+数索引本身并不能找到具体的一条记录,只能找到该记录所在的页。数据库把页载入内存,然后通过Page Directory再进行二叉查找。
File Trailer只有一个FIL_PAGE_END_LSN部分,8个字节。前4个字节代表该页的checksum值,最后4个字节和File Header中的FIL_PAGE_LSN相同,以此保证页的完整性。
【http://forge.mysql.com/wiki/MySQL_Internals_InnoDB里面有一些额外的介绍】
注:如若涉及版权或者其他问题,请联系本人。 |