Innodb是MySQL默认存储引擎,Innodb中数据如何存储关系到数据的读写性能和存储成本,因此了解其存储格式有助于理解sql语句的执行成本。
Innodb数据都是存储在数据页,内存页大小16K,索引B+树就是以页来组织的。
行记录存储格式
数据按行存储,行存储格式为:
变长字段长度列表、null字段列表、行记录头、数据列(列1:列2:...:列N)
- 变长字段长度列表:逆序存储了变长字段数据实际长度(占用空间字节长度,不同编码长度不同,比如ascii占用1字节,utf8mb4占用1-4字节),比如只有列1和列2是变长字段,那么这里顺序其实是
列2长度:列1长度
,逆序是为了缓存优化,让数据和数据长度尽量离的更近,注意varchar/test/blob
都属于变长字段。如果字段实际长度大于127,则使用2个字节,如果区分哪些字节在一起表示某个字段长度呢?单字节最高bit位为0表示单独字节表示某个长度,否则不是。 - null字段列表:每个可为null字段对应该字段中一个bit位,也是逆序存储,如果对应bit为1表示该字段为null否则非null,null字段列表最小1个字节可存储8个可null字段。
- 行记录头:一些行记录头部信息。
- 数据列:N个数据列紧密存储,不留空白。
注意,实际的innodb数据行记录还包括trx_id和roll_pointer,如果没有唯一主键或索引,那么会默认生成row_id作为唯一主键。
innodb定义了4种行格式:
- compact行格式,就是上面所说的;
- redundant行格式,就是将上面所说的
变长字段长度列表+null字段列表
整合成一个字段-字段长度偏移列表,其他的是一样的; - dynamic和compressed行格式:和compact不同的是,处理溢出列时不会在真实的记录数据位置存储数据的钱768字节,而是只存储个指针指向溢出空间,另外compressed行格式会采用压缩算法对页面进行压缩。
行记录头信息如下:
- deleted_flag为1表示被删除,这里只是设置delete标志,还没有真正删除数据。
- record_type=0表示该记录是普通数据,一个数据页都会有一个infimum记录和supremum记录,它们都是没有主键的,这是innodb数据页中规定的,infimum表示当前页中最小的记录,supremum表示当前页中最大的记录。
- next_record是下一条记录的偏移量(字节偏移量),如果为负表示下一数据在该数据前面,每个记录的next_record将页中所有数据串联成一个链表。
页数据存储格式
Innodb为不同数据设计了不同的数据页,存储数据记录页称为数据页,一个数据页格式如下:
最大最小记录也就是infimum记录和supremum记录。Innodb将页中记录分为若干组,每个组最后一个记录的偏移量作为一个槽,记录在页目录中(Page Directory),一个槽占用2字节。
页目录相当于当前页面记录主键id的索引,辅助快速在页中查询id用。文件尾存储页数据校验和、业数据修改日志序列位置(LSN),校验和用于校验文件数据是否完整。
通过主键在页中查找数据是很快的,通过主键id查找流程如下:
- 二分法找到主键id距离最近的id偏移量;
- 然后从最近id偏移量处 遍历数据找到即可。
Page Header是专门针对数据页记录的各种状态信息,比方说页里头有多少个记录,有多少个槽。我们现在描述的File Header针对各种类型的页都通用,也就是说不同类型的页都会 以File Header作为第一个组成部分,它描述了一些针对各种页都通用的一些信息,比方说这个页的编号是多少,它的上一个页、下一个页是谁等,格式如下:
- FIL_PAGE_PREV和FIL_PAGE_NEXT分别用于指向前后页,将这些页面串起来。
- FIL_PAGE_TYPE表示页类型,Innodb为不同目的设计了不同的页,常见的就是数据页,当然也有如下几种类型:
注意,数据页是innodb管理各种数据的基本单位,不是使用时不是单页申请的,而是从系统申请一块区域作为页空间。
参考资料: