浅析innoDB数据存储结构
No.1
引言
InnoDB是我们日常开发中最常使用的Mysql存储引擎,对于这个“老朋友”,你知道他是如何存储数据记录的吗,你知道他是如何建立索引的吗?本文将会和大家一起深入理解下innoDB数据存储结构,讲讲逻辑上的存储结构和“页”的数据结构,让你做innoDB的真兄弟!
No.2
InnoDB体系结构
由图可知,Mysql数据库可简单理解为3部分
-
操作文件(数据)的后台线程
-
减少cpu与磁盘速度差异的内存池
-
存储索引,数据等信息的文件集合
而本文要介绍的innoDB存储结构实质上就是指innoDB在存储文件这一部分的数据结构。
数据库文件中需要存储哪些数据?
-
数据
-
索引
-
表结构
-
日志:包括错误日志,慢查询日志,查询日志,
二进制日志,undo,redo
-
其他
数据库文件物理分布
在具体讲解逻辑存储结构前,我们先简单了解下物理上这些文件是怎么分布的。物理分布如下图所示。
-
.frm文件
此类型文件用于存储表结构,此结构与使用引擎无关,每张表都存此文件。特别的是当创建一个视图时,也会生成此类新文件用于记录视图的定义。
-
.ibd文件
此类型文件为表空间
-
innoDB默认情况下会存在一个10M大小的表空间ibdata1,innoDB管理的表的数据都会存储在此表空间中
-
可以通过设置innodb_data_file_path来指定多个文件组成一个表空间,若多个文件不在同一个磁盘下,则可以平均磁盘的负载,提升性能。
-
可以通过设置innodb_file_per_table来设置每个表生成单独的ibd表空间,这样数据就不会存在默认的表空间下了。
-
特别注意:此处大家可能无法理解表空间是个什么概念,大家先记住.ibd存储是表空间文件即可,后面会进行讲解。
No.3
innoDB逻辑存储结构
数据都被逻辑的放到一个空间里,称之为表空间(tablespace),表空间又由段(segment)、区(extent)、页(page)组成 ,页innoDB文件管理的最小单位。
-
一个段256M (不可变)
-
一个区1M (不可变)
-
一个页16K (可变)
-
一个段=256区=256*64个页
表空间结构
由上图可知,表空间是处于结构的最外层。所有的数据都存放在表空间中,文件格式为.ibd。
特别注意的是,如果我们设置了每个表对应一个表空间,那共享的表空间是不是就没有用了呢?
答案是否定的,对于每个表独立的表空间来说只存数据、索引、插入缓冲Bitmap页,其他如回滚信息、系统事务信息等还在共享表空间中。
Segment-段
表空间由各个段组成,常见的段有数据段、索引段、回滚段等
数据段即叶子节点
索引段即非叶子节点
Extend-区
区是由连续的页组成的空间,在任何情况下每个区的大小都是1M,默认情况下每个页的大小是16KB,即一个区有64个页。
Page-页
页是InnoDB磁盘管理的最小单元,默认每页16KB,可通过innodb_page_size设置大小为4K,8K,16K,但设置完成后不可更改。
常见页类型:
数据页 , undo页,系统页,事务数据页,插入缓冲位图页
插入缓冲空闲列表页,未压缩的二进制大对象页,压缩的二进制大对象页
段-区-页组织结构
对于一个表空间来说至少会有个0号页面(FSP_HDR)用于存储extend相关的信息,记录了哪些空闲,哪些满,哪些有碎片等信息,对于一个FSP_HDR页来讲,只能维护256个extend信息,所以每隔256M就会有一个XDES用来维护下面的区的信息。那为什么是256个区呢?下图分别给出了FSP(XDES)和 XDES entry的结构。
由上图可知,每个区对应40byte的记录,那16kb扣除其他信息,可存256条记录,所以一个FSP/XDES页可存256个区信息,同时也就导致了每个256M就需要有一个XDES页。
总结一下
综上所诉,一个表空间有很多页,为了更高效对页进行管理呢,每64个页划分为一组就是区,每256个区再划分为一个段,我个人认为区和段并不是真实存在的,只是一个逻辑概念。
No.4
INNODB数据页结构
通过上文相信大家对innoDB的数据结构已经有了一个整体上概念,也应该知道了页是innoDB存储引擎能操作的最小磁盘单位,页类型是B-tree的页存放的即是表中行的实际数据,下面我们继续深入了解数据页的结构。
组成部分
其中File Header 和 PageHeader 和 Infimun + SupremumRecords,File Trailer是大小固定的用于存储页的一些相关信息,其余的是和数据相关的则为不定长。
File Header
名称 | 大小(字节) | 说明 |
FIL_PAGE_SPACE_OR_CHECKSUM | 4 | 代表了页的checksum值 |
FIL_PAGE_OFFSET | 4 | 表空间中页的偏移值 |
FIL_PAGE_PREV | 4 | 当前页的上一个页 |
FIL_PAGE_NEXT | 4 | 当前页的下一个页 |
FIL_PAGE_TYPE | 2 | 页类型 |
FIL_PAGE_LSN | 8 | 该值代表了该页最后被修改的日志序列位置 |
FIL_PAGE_FILE_FLUSH_LSN | 8 | 该值仅在系统表空间定义,独立表空间为0 |
FIL_PAGE_ARCH_LOG_NO_SPACE_ID | 4 | 页所属表空间 |
页在逻辑上是连续的,在磁盘中并不是连续的,而是通过上下指针进行逻辑关联,构成双向链表。但是页内的物理地址是连续的,页内的记录是乱序的单向链表。
Page Header
记录数据页的状态信息,14部分组成,共56字节
名称 | 大小(字节) | 说明 |
PAGE_N_DIR_SLOTS | 2 | Page Directory的Solt数 |
PAGE_HEAP_TOP | 2 | 指向空闲空间的起始地址 |
PAGE_N_HEAP | 2 | 页记录数,包含已经标记删除的 |
PAGE_FREE | 2 | 指向可重用空间的首指针 |
PAGE_GARBAGE | 2 | 已删除记录总数 |
PAGE_LAST_INSERT | 2 | 最后插入记录位置 |
PAGE_N_RECS | 2 | 页记录数,不包含已经标记删除的 |
PAGE_LEVEL | 2 | 当前页在索引数的位置 |
PAGE_INDEX_ID | 8 | 索引id,当前页属于哪个索引 |
PAGE_BTR_SEG_LEAF | 10 | B+树root页定义,非页子节点所在段 |
PAGE_BTR_SEG_TOP | 10 | B+树root页定义,非页子节点所在段 |
todo 差表格
Infimun和SupremunRecords
虚拟的行记录,可认为是占位符,用来限定记录边界,Infimun记录是比该页主键值都要小的值,Supermun是比任何可能大的值还要大的值,两个记录在页创建时候创建
UserRecords(行记录)
InnoDB存储引擎提供了Compact和Redundant两种格式来存放行记录数据。redundant是为了兼容之前版本而保留的我们不做过多研究,我们简单了解下Compact格式。
Compact格式 :compact行记录格式是从5.0引入,然后mysql5.1开始 Compact成为了默认的存储记录格式。
-
可以通过SHOW TABLE STATUS LIKE 'table_name',查看当前使用的格式。
-
可以使用CREATE TABLE 表名(列信息)ROW_FORMAT=COMPACT进行指定行格式。
-
可以使用ALTER TABLE 表名 ROW_FORMAT = 行格式名称进行更改行格式
上图为Compact行记录格式。
变长字段长度列表:当列长度小于255时,用一个字节表示,当列长度>255时,用2个字节表示,由此也可以解释varchar的长度最长为65535 (2的16次幂),刚好是能记录的最大长度。
NULL标志位:很好理解标志该行数据中是否有空值
记录头信息 :如下图所示
行溢出 :
关于行溢出,即存储很长的字符串,在该字段会存储该字符串的前768个字节的前缀(字段超过768字节则为变长字段),并将整个字符串存储在uncompress blob页中。
行分布
前面说到过,页的物理地址是连续的,但是页内数据却是乱序的,理解起来可能有些抽象,下图将直观的展示出页内行记录的分布。
那我们在结合着上文说到的页和页之间构成双向链表的关系,下面给出了页结合行的结构图。
Page Directory
页目录,放了记录的相对位置,它是一个稀疏目录,一个目录指针(solts)包含多个记录,B+树索引本身并不能找到具体的一条记录,B+树索引能找到只是该记录所在的页。数据库把页载入内存,然后通过PageDirectory再进行二叉查找。只不过二叉查找的时间复杂度低,同时内存中的查找很快,因此通过忽略了这部分查找所用的时间。
Free Space
这一部分存储着释放的空间,这些空间构成了空闲空间链表。
BTree
根据上文描述的页结构最终可构建出innoDB的索引结构图,索引页为非叶节点 ,索引节点中存有对应数据页的指针, 数据页为叶子节点,所以我们通过索引查找记录,是先定位到页,再通过页内的稀疏索引查找对应记录。
END
后期会有专门讲解innoDB索引结构的分享哦!别忘了结合本文一起看奥!相信会有1+1>2的效果!
特别声明
本文部分插图来源于《mysql技术内幕-innoDB存储引擎第2版》和网络。
欢迎留言沟通!