从底层理解MySQL的存储引擎InnoDB(一)
前言
最近打算深入地去理解MySQL底层的实现原理。比如常用的存储引擎InnoDB的数据结构。参考了很多资料,但是总感觉知识点是断断续续的。比如很多文章一开始就在讲InnoDB的行格式和数据页结构。我就一头雾水,就是不知道这些概念是怎么引出来的?
偶然发现一本宝藏书籍:《mysql 技术内幕:innodb存储引擎第2版》比较系统地进行了讲述。其中最感兴趣的是第2,4,5章。但是我还没有读完这本书,所以这里打算做一个深入理解MySQL系列的读书笔记,陆陆续续地记录下MySQL的整个实现原理和底层数据结构,其中包括我自己的理解。完全照搬书籍的话也就没有意义了,毕竟还是有选择的阅读,实在晦涩难懂的部分,也就选择性地放弃啦,哈哈~~~
路过的要是有感兴趣的盆友,欢迎下方留言,大家可以一起交流下呀~~~
一、InnoDB的最小存储单元
1、 最小存储单元
为什么要说到最小存储单元呢?这要从计算机的存储说起。计算机在存储数据的时候,有最小存储单元,这就好比现金的流通最小单位是一毛。
在计算机中,最小存储单位分别为:
1)磁盘:扇区(512byte)
2)文件系统:块(4kb)
3)InnoDB:页(16kb)
2、要解决的问题
大家都知道mysql中数据是存储在物理磁盘上的,而真正的数据处理又是在内存中执行的。由于磁盘的读写速度非常慢,如果每次操作都对磁盘进行频繁读写的话,那么性能一定非常差。
为了解决上述问题,InnoDB将数据划分为若干页,以页作为磁盘与内存交互的基本单位。这样的话,一次性至少读取1页数据到内存中或者将1页数据写入磁盘。通过减少内存与磁盘的交互次数,从而提升性能。
其实,这本质上就是一种典型的缓存设计思想,一般缓存的设计基本都是从时间维度或者空间维度进行考量的:
1)时间维度:如果一条数据正在在被使用,那么在接下来一段时间内大概率还会再被使用。可以认为热点数据缓存都属于这种思路的实现。
2)空间维度:如果一条数据正在在被使用,那么存储在它附近的数据大概率也会很快被使用。InnoDB的数据页和操作系统的页缓存则是这种思路的体现。
那么mysql中的数据是如何存放的,以及页的数据结构又是如何的呢?这就引出了我们下面要提及的InnoDB的行格式和数据页的结构。
二、InnoDB行格式
1、行格式的定义
MySQL是以记录(一行数据)为单位向数据表中插入数据的,这些记录在磁盘上的存放方式称为行格式。
MySQL 目前有四种行记录格式:Compact、Redundant、Dynamic 和 Compressed。其中 Redundant 是以前使用的旧格式,为了兼容性还一直保留。自 MySQL 5.1 开始,默认的行记录格式为 Compact,而从 MySQL 5.7 开始,默认的行记录格式为 Dynamic。使用 SHOW TABLE STATUS LIKE 'table_name' 可以查看指定表的行格式。
DYNAMIC行格式提供与COMPACT行格式相同的存储特性,但是增加了对可变长度列的增强的存储功能,并支持大索引键前缀。
2、COMPACT
COMPACT的设计目标是能高效存放数据。简单来说,如果一个页中存放的行数据越多,其性能就越高。Compact行记录以如下方式进行存储:
从上图可以看出,一条完整的记录包含记录的额外信息和记录的真实数据两大部分。
下面通过Compact行格式的实现来初步看看MySQL的记录存储智慧。
我们先创建一个行格式为Compact,字符集为ascii的数据表tbTest,sql如下:
1 CREATE TABLE `tbTest` ( 2 `c1` varchar(10), 3 `c2` varchar(10) NOT NULL, 4 `c3` char(10), 5 `c4` varchar(10) 6 ) ENGINE=InnoDB DEFAULT CHARSET=ascii ROW_FORMAT=COMPACT; 7 8 insert into tbTest(c1,c2,c3,c4) values ('aaaa','bbb','cc','d'); 9 insert into tbTest(c1,c2,c3,c4) values ('eeee','fff',NULL,NULL);
2.1 记录的额外信息
记录的额外信息主要包含3类:变长字段长度列表、NULL值列表和记录头信息。
变长字段长度列表
mysql中支持一些变长数据类型(比如VARCHAR(M)、TEXT等),它们存储数据占用的存储空间不是固定的,而是会随着存储内容的变化而变化。为了准确描述这种数据,这种变长字段占用的存储空间要同时包含:真正的数据内容 和 占用的字节数
当列的长度小于255字节,用1字节表示,若大于255个字节,用2个字节表示。
前面我们创建的tbTest的表,插入的两条数据,
第1条记录变长字段长度列表的存储格式: 01 03 04
第2条记录变长字段长度列表的存储格式: 03 04
需要注意的地方:
1)这些变长列的实际占用字节数以逆序方式存储在变长字段长度列表中。
2)允许的最大字节超过255且实际存储超过127字节,使用两个字节存储其长度, 否则使用一个字节
3)变长字段长度列表中只存储值为非NULL 的列内容占用的长度,值为NULL的列的长度是不储存的
4)第一个字节的第一位是标志位,表示是否双字节标识
说明: InnoDB在读字段变长列表时会先查表结构,允许的最大字节数超过255时才会使用这个二进制位作为标识位来判断是读一个字节还是两个字节,没有超过就直接读一个字节也就不存在标识位了
NULL值列表
对于可为NULL的列,为了节约存储空间,mysql不会将NULL值保存在记录的真实数据部分。而是会将其保存在记录的额外信息里面的NULL值列表中。
具体的做法:先统计表中允许存储NULL值的列,然后将每个允许存储NULL值的列对应一个二进制位(1:值为NULL,0:值不为NULL)用来表示是否存储NULL值,并按照逆序排列。
比如,这里的tbTest表中,c1,c3,c4是允许为NULL的,然后每一行记录中,每个列对应一个二进制位,对应的值如果为NULL的,就记录为1,不为NULL的就记录为0。然后按照逆序排列。
按照上面的规则得到:
第一条记录NUll值列表的存储格式为:000
第一条记录NUll值列表的存储格式为:110
此部分由整数个字节组成(也就是由二进制转化为十进制), 不足地方高位补0,因此第一条记录显示:00,第二条记录显示06.
记录头信息
记录头信息是由固定的5个字节(40bit)组成, 不同的位代表不同的含义:
需要注意的地方:
1)delete_mask: 被删除的记录值为1, 正常记录为0(不会做物理删除,方便查询)
2)next_record:
- 当前记录的真实数据位置距离下一条记录的真实数据的偏移量(可以当做存了个指针,向后是额外信息,向前是具体的列)
- 根据这个属性,页面内所有记录都串了一个单链表
- 单链表按主键排序,从小到大,最小记录与最大记录分别为头结点和尾节点
2.2 记录的真实数据
记录的真实数据除了包含各列具体的数据外,还会自动添加一些隐藏列数据。
只有当数据库没有定义主键或者唯一键时,隐藏列row_id才会存在,并且将其作为数据表主键。因为表tbTest并没有定义主键,所以MySQL服务器会为每条记录增加上述的3个列。
鉴于篇幅长度的考虑,InnoDB数据页结构放到下一篇笔记来讲。
参考链接:https://zhuanlan.zhihu.com/p/180531140