MysqlInnoDB存储引擎数据结构
磁盘存储的局部性原则
先来看一个基础查询语句
select * from a where c > 1;
分析一下sql的实现过程,按照通常理解,首先会找到c = 1的这条数据放到内存里,然后由cpu拿到c =1的这个值与c > 1的条件进行比较运算,然后逐条筛选符合条件的数据。这样的话显而易见查询结果的性能非常低下,因为要逐条从磁盘读取数据。所以为了避免这种状况,这时候操作系统会取此数据相邻的一些数据放入内存中,当下次有用到这些数据的时候直接从内存中去取而不用去读磁盘,这就是所谓的局部性原则。
局部性原理:一个良好的计算机程序 常常具有良好的局部性,也就是说,它们倾向于引用邻近于其他最近引用过的数据项的数据项,或者最近引用过的数据项本身。
那么问题来了,回到查询语句上,按照局部性原理,那么操作系统应该取多少数据缓存到内存中,这就引出了一个新的存储单位:页。mysql的一行数据物理存储是具体存储到某一页上,所以操作系统会取出c=1的这行数据所在的页缓存到内存中,一页的大小默认为4kb。
Innodb的存储结构
Innodb既然作为一个存储引擎,自然也会遵守局部性原则,innodb的页大小默认为16kb。
如图,简单介绍一下
- file header记录page的头部信息,重要的是里面存储了下个页的地址信息与上个页的地址信息,所以innodb页是一个双向链表。
- page header 用于记录page的状态信息。
- 第三行则是标明此页存储的最小和最大边界值,即存储在这页上的最小主键值要大于最小边界值小于最大边界值。
- user records存储真正的mysql数据。
- free space顾名思义,剩余空间
- page directory 即目录,存储页中某些数据的相对位置
- file trailer 校验页是否完整
行记录
mysql的数据都是按行存储的,所以存储到磁盘上也是有对应的行结构。
Compact 行记录
- 变长字段长度列表:一到两个字节,逆序记录每一个列的长度。该字段的实际长度取决于列数和每一列的长度,因此是变长的。举例来说,varchar类型就是变长的,而int类型就是固长的。
- NULL 标志位:一个字节,表示该行是否有 NULL 值。
- 记录头信息:其中 next_record 记录了下一条记录的相对位置,一个页中的所有记录使用这个字段形成了一条单链表。
- 列1,列2:即真正的sql列数据
- 隐藏列: Transaction ID、Roll Pointer 以及 row_id(当没有指定主键)。
行溢出
除掉blob类型,mysql一行数据大小最大值是65535个字节,但并不代表你创建表给表中某个字段最大值为65535.。如下
crate table a (
c varchar(65535))
像这样则创建失败,因为行的最大长度65535包括的变长列表,null标志位等。
页溢出
我们知道数据页的大小是 16KB,如果一页当中的记录过大,则会进行分页处理,前页会存储下页地址。
索引
读者都知道innodb的索引数据结构为b+ 树。之前提到页的结构中介绍过page directory,mysql页中的数据是以主键递增单链表形式存储,而链表的缺点很明显,查询效率慢,所以page directory就为解决这种状况出现,将主键进行分组存储到目录中,再查询的时候利用二分法查询目录,确定要在哪组进行遍历,从而提升查询速度。
如图,假如查询8_1a 这条数据,先在目录进行比较,主键为8,在目录下比较后是大于4小于10,就去4目录分组下的去递归遍历。
这是一个页内的查找状况,但实际查询数据你无法你需要的数据确定在哪一页,这时候通过页内的目录查找思想灵活运用,即每页的页号会以k,v的形式存储,k为页内的最小主键值,v为页的实际存储地址。
即如图
b+ 树就是这样一个个节点累积而成的。