MySQL中InnoDB存储方式总结

934 阅读7分钟

根据小孩子4919的《MySQL是怎样运行的:从根儿上理解MySQL》总结的

一、存储的结构

InnoDB存储引擎存储记录需要从宏观到微观的方式去看,首先是按照B+树的方式去存储数据的,叶子节点存放的是真实的数据记录,也称作==用户记录==,而在非叶子节点存放的是数据页的信息,称作==目录项记录==。

image_1ca80gps314u9121u1rdp9r7md8cm.png-55.6kB

整体的结构如上面所示,再微观一点的看呢,每一个节点存储的都是一个页,每一个页都存放了一些数据,如下图。同一高度的相邻页都是按照双向链表连接的,页中的相邻数据则是按照单项链表连接的。==页组成的双向链表==可以方便快速找到上一页和下一页,==数据组成的单向链表==可以通过分组和插槽进行二分法快速定位。

image_1cacafpso19vpkik1j5rtrd17cm3a.png-158.1kB

每一个页到达一定规模存储不下的时候,都会分裂成新的页,同时在它的父节点处加一条页的数据。同理,目录项记录变多也会导致页的分裂,在父节点添加数据.....==目录项记录==存储以下内容,页当中最小的主键值key(方便后续查找),页号page_no

image_1caba0afo11fa1cli1nu070m16bg1j.png-119.1kB

如果是按照主键进行排序存储的话,页当中存储的就是完整的记录,这个索引也被称作==聚簇索引==,对于那些也想有序存储以便于快速查找的列,也可以建立索引,但是页中记录只存储==索引列的值==,==主键值==,这时要查询就是要先查询索引列的值,然后匹配主键列的值,再根据主键==回表查询==,这种方式的索引叫==非聚簇索引==。

二、数据页的结构

一个完整的表的存储方式就是按照B+树索引方式存储(这只是InnoDB的一种存储方式),数据页里也包含几个部分,一页默认是16KB,7个部分,图如下:

img

各部分组成及作用如下表:

名称中文名占用空间大小简单描述
File Header文件头部38字节页的一些通用信息
Page Header页面头部56字节数据页专有的一些信息
Infimum + Supremum最小记录和最大记录26字节两个虚拟的行记录
User Record用户记录不确定实际存储的行记录内容
Free Space空闲空间不确定页中尚未使用的空间
Page Directory页面目录不确定页中的某些记录的相对位置
File Trailer文件尾部8字节校验页是否完整

各部分更加详细的内容如下:

  • Free Space:表示页当中未使用的空间
  • User Record:记录的数据是存储在这个部分,最开始没有,每放入一条记录会从Free Space中分配出来,页空间用完了会申请新页

image_1cosvi1in9st476cdqfki1n39m.png-133.8kB

  • Infimum和Supremum:规定这两个是页中最小值和最大值

image_1c9ra45eam7t1mil9o1h3ucqdhv.png-50.4kB

页中的数据是按照链表的方式指向的,按照索引的顺序指向,从一条记录指向下一条记录。

image_1cot1r96210ph1jng1td41ouj85c13.png-120.5kB

删除一条记录,并不是直接把这条记录从页中删除,而是给它标记成已删除,记录还在,但指向不在了,这条记录会放入一个叫垃圾链表的地方,这样如果后面恢复也可以很快恢复,不会经常的改变索引的结构。

image_1cul8slbp1om0p31b3u1be11gco9.png-119.6kB

  • Page Directory:页中的数据记录会进行分组,每组都包含一些数据,组中==最后一条记录==与页面中第0个字节的距离(地址偏移量)叫做==槽==。如下图,槽对应着每组最大的记录,这样查找时可以通过二分法去找到位于中间的槽,进而找到槽对应的最大记录的主键与期望查找的主键比较,然后再查找。

image_1d6g64af2sgj1816ktl1q22dehp.png-189.1kB

  • Page Header:存储了页的一些信息
名称占用空间大小描述
PAGE_N_DIR_SLOTS2字节在页目录中的槽数量
PAGE_HEAP_TOP2字节还未使用的空间最小地址,也就是说从该地址之后就是Free Space
PAGE_N_HEAP2字节本页中的记录的数量(包括最小和最大记录以及标记为删除的记录)
PAGE_FREE2字节第一个已经标记为删除的记录地址(各个已删除的记录通过next_record也会组成一个单链表,这个单链表中的记录可以被重新利用)
PAGE_GARBAGE2字节已删除记录占用的字节数
PAGE_LAST_INSERT2字节最后插入记录的位置
PAGE_DIRECTION2字节记录插入的方向
PAGE_N_DIRECTION2字节一个方向连续插入的记录数量
PAGE_N_RECS2字节该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录)
PAGE_MAX_TRX_ID8字节修改当前页的最大事务ID,该值仅在二级索引中定义
PAGE_LEVEL2字节当前页在B+树中所处的层级
PAGE_INDEX_ID8字节索引ID,表示当前页属于哪个索引
PAGE_BTR_SEG_LEAF10字节B+树叶子段的头部信息,仅在B+树的Root页定义
PAGE_BTR_SEG_TOP10字节B+树非叶子段的头部信息,仅在B+树的Root页定义
  • File Header:记录比较通用的一些文件头信息
名称占用空间大小描述
FIL_PAGE_SPACE_OR_CHKSUM4字节页的校验和(checksum值)
FIL_PAGE_OFFSET4字节页号
FIL_PAGE_PREV4字节上一个页的页号
FIL_PAGE_NEXT4字节下一个页的页号
FIL_PAGE_LSN8字节页面被最后修改时对应的日志序列位置(英文名是:Log Sequence Number)
FIL_PAGE_TYP2字节该页的类型
FIL_PAGE_FILE_FLUSH_LSN8字节仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID4字节页属于哪个表空间

==FIL_PAGE_PREV==和==FIL_PAGE_NEXT==记录的是上一个页号和下一个页号,所以这些数据页构成了一个大的链表。

image_1ca00fhg418pl1f1a1iav1uo3aou9.png-90.9kB

  • File Trailer:文件的尾信息,由8个字节组成,前4个字节代表页的校验和,如果修改记录的时候突然断电,避免出现同步异常,同步之前先计算个校验和,存储到File Header中,存储于磁盘中,等到同步的时候,会从File Trailor中比较校验和,不同步就是中间出错了。

三、记录的结构

InnoDB页存储的记录是有几种格式的:==Compact行格式,Redundant行格式,Dynamic行格式,Compressed行格式==。这里主要是Compress行格式的结构:

1、记录的额外信息

image_1c9g4t114n0j1gkro2r1h8h1d1t16.png

  • 变长字段长度列表:有一些可变长字段如果按真实存储会很麻烦,所以存在变长字段长度列表中,变长字段长度列表==只存储非Null列的长度==,而且是==逆序存放==。
  • Null值长度列表:对于Null值会存储到这个部分,先记录能存放Null值的列,然后同样是按照Null值字段==逆序存放==。==二进制值为1,则列值为Null,二进制值为0,则列值不为Null,这些字段的值按整数个字节存放。==

image_1c9g8ps5c1snv1bhj3m48151sfl6r.png-20.6kB

  • 记录头信息:用于存放该记录的一些其他信息

image_1c9geiglj1ah31meo80ci8n1eli8f.png-29.5kB

名称大小(单位:bit)描述
预留位11没有使用
预留位21没有使用
delete_mask1标记该记录是否被删除
min_rec_mask1B+树的每层非叶子节点中的最小记录都会添加该标记
n_owned4表示当前记录拥有的记录数
heap_no13表示当前记录在记录堆的位置信息
record_type3表示当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录
next_record16表示下一条记录的相对位置

2、记录的真实数据

MySQL会给记录加上一些隐藏列:

列名是否必须占用空间描述
row_id6字节行ID,唯一标识一条记录
transaction_id6字节事务ID
roll_pointer7字节回滚指针

InnoDB表对主键的==生成策略==:优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个Unique键作为主键,如果表中连Unique键都没有定义的话,则InnoDB会为表默认添加一个名为row_id的隐藏列作为主键。