1、存储引擎概念
使用不同的存储机制、索引技巧、锁定水平将数据存储在文件系统中的存储方式或者存储格式,并最终提供不同的功能和能力,这些不同的技术以及配套的功能在MySQL中称为存储引擎。
存储引擎是MySQL数据库中的组件,负责执行实际的数据I/O操作。
MySQL系统中,存储引擎处于文件系统之上,在数据保存到数据文件之前会传输到存储引擎,之后按照各个存储引擎的存储格式进行存储。
2、MySQL的工作流程
我们先介绍下mysql的工作流程的情况,以便能清楚的知道我们主角存储引擎在mysql中的一个大概位置。

MySQL架构总共四层,如上图
连接层,大多数网络服务工具都有类似的架构。细分为:连接处理、授权认证、安全等。
服务层,包括:查询解析、分析、优化、缓存以及所有的内置函数。所有的跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。
引擎层。存储引擎负责MySQL中数据的存储和提取。服务器通过API和存储引擎进行通信。这些接口屏蔽了不同存储引擎之间的差异,使得这些差异对上层的查询过程透明化。存储引擎API包含十几个底层函数,用于执行“开始一个事务”等操作。但存储引擎一般不会去解析SQL(InnoDB会解析外键定义,因为其本身没有实现该功能),不同存储引擎之间也不会相互通信,而只是简单的响应上层的服务器请求。
存储层,所有的表结构和数据以及用户操作的日志最终还是以文件的形式存储在硬盘上。
3、MySQL支持的存储引擎
show engines;
复制

InnoDB:MySql 5.6 版本默认的存储引擎。InnoDB 是一个事务安全的存储引擎,它具备提交、回滚以及崩溃恢复的功能以保护用户数据。InnoDB 的行级别锁定以及 Oracle 风格的一致性无锁读提升了它的多用户并发数以及性能。InnoDB 将用户数据存储在聚集索引中以减少基于主键的普通查询所带来的 I/O 开销。为了保证数据的完整性,InnoDB 还支持外键约束。
MRG_MyISAM:使MySQL DBA或开发人员能够对一系列相同的MyISAM表进行逻辑分组,并将它们作为一个对象引用。适用于VLDB(Very Large Data Base)环境,如数据仓库;
Memory:在内存中存储所有数据,应用于对非关键数据快速查找的场景。Memory类型的表访问数据非常快,因为它的数据是存放在内存中的,并且默认使用HASH索引,但是一旦服务关闭,表中的数据就会丢失
BLACKHOLE:黑洞存储引擎,没有存储机制,任何发往次引擎的数据都会丢弃,其会记录二进制日志,因此,常用于多级复制架构中作中转服务器
MyISAM:MyISAM既不支持事务、也不支持外键、其优势是访问速度快,但是表级别的锁定限制了它在读写负载方面的性能,因此它经常应用于只读或者以读为主的数据场景。
Archive (归档):为存储和检索大量很少参考的存档或安全审核信息,·只支持SELECT和INSERT操作;支持行级锁和专用缓存区;
Performance_Schema:Performance_Schema数据库;
Federated:提供了从多个物理机上连接不同的 MySql 服务器来创建一个逻辑数据库的能力。适用于分布式或者数据市场的场景。
NDB:是MySQL CLUSTER中专用的存储引擎。它是集群用的一种存储引擎,但是这种集群案例用的几乎寥寥无几
4、InnoDB源代码目录结构
mysql源码目录下的storage目录就是关于存储引擎的,里面有一个innobase目录,如下:

目录名解释说明:

5、InnoDB底层存储文件

InnoDB表在底层是2个文件:1个frm文件,存储表结构定义信息;1个ibd文件,存放表索引和数据

6、InnoDB内存及磁盘架构

缓冲池(Buffer Pool)
缓冲池,简单来说就是一块内存区域。它存在的原因之一是为了避免每次都去访问磁盘,把最常访问的数据放在缓存里,提高数据的访问速度。BP以Page页为单位,默认大小16K,BP的底层采用链表数据结构管理Page。在InnoDB访问表记录和索引时会在Page页中缓存,以后使用可以减少磁 盘IO操作,提升效率。
Page管理机制
free page :空闲page,未被使用
clean page:被使用page,数据没有被修改过
dirty page:脏页,被使用page,数据被修改过,页中数据和磁盘的数据产生了不 一致
针对上述三种不同的Page类型,Innodb有定义三种不同的链表用于管理Page
free list :表示空闲缓冲区,管理free page
flush list:表示需要刷新到磁盘的缓冲区,管理dirty page,内部page按修改时间排序。脏页即存在于flush链表,也在LRU链表中,但是两种互不影响LRU链表负责管理page的可用性和释放,而flush链表负责管理脏页的刷盘操作。最早修改的在链表的尾部,所以刷盘从尾部开始刷盘
lru list:表示正在使用的缓冲区,管理clean page和dirty page,缓冲区以 midpoint为基点,前面链表称为new列表区,存放经常访问的数据(热点数据),占63%;后面的链表称为old列表区,存放较少数据,占37%。
改进型LRU算法维护
普通LRU:末尾淘汰法,新数据从链表头部加入,释放空间时从末尾淘汰。
改性LRU:链表分为new和old两个部分,加入元素时并不是从表头插入,而是从中间midpoint位置插入,如果数据很快被访问,那么page就会向new列表头部移动,如果数据没有被访问,会逐步向old尾部移动,等待淘汰。每当有新的page数据读取到buffer pool时,Innodb引擎会判断是否有空闲页,是否足够,如果有就将free page从free list列表删除,放入到LRU列表中。没有空闲页,就会根据LRU算法淘汰LRU链表默认的页,将内存空间释放分配给新的页。
写缓冲区(Change Buffer)
当更新一条记录时,该记录在BufferPool存在,直接在BufferPool修改,一次内存操作。如果该记录在BufferPool不存在(没有命中),会直接在ChangeBuffer进行一次内存操作,不用再去磁盘查询数据,避免一次磁盘IO。当下次查询记录时,会先进磁盘读取,然后再从 ChangeBuffer中读取信息合并,最终载入BufferPool中。
★ 写缓冲区,仅适用于非唯一普通索引页:
如果在索引设置唯一性,在进行修改时,InnoDB必须要做唯一性校验,因此必须查询磁盘,做一次IO操作。会直接将记录查询到BufferPool中,然后在缓冲池修改,不会在 ChangeBuffer操作
日志缓冲区(Log Buffer)
当在MySQL中对InnoDB表进行更改时,这些更改首先存储在InnoDB日志缓冲区的内存中,然后写入redo logs的文件中。日志缓冲区是内存存储区域,用于保存要写入磁盘上的日志文件的数据。日志缓冲区大小由innodb_log_buffer_size 变量定义,默认大小为16MB。
自适应哈希索引(Adaptive Hash Index)
自适应hash索引是一种键值对的存储结构,存储的是热点页所在的记录。InnoDB存储引擎会自动根据访问的频率和模式来为某些页建立哈希索引。
系统表空间(The System Tablespace)
就是我们常说的共享表空间,系统表空间(在操作系统上体现就是ibdata文件)是我们在初始化mysql实例时生成的(在初始化mysql实例时会读取my.cnf中的innodb_data_file_path参数,然后初始出相应的文件ibdata1、ibdata2 …,至于文件多大,有多少个,看你my.cnf中的参数是怎样设置的)。
独立表空间(File-Per-Table Tablespaces)
独立表空间是一个单表表空间,该表创建于自己的数据文件中,而非创建于系统表空间中。当innodb_file_per_table选项开启时,表将被创建于表空间中。否则, innodb将被创建于系统表空间中。每个表文件表空间由一个.ibd数据文件代表,该文件默认被创建于数据库目录中。表空间的表文件支持动态(dynamic)和压缩 (commpressed)行记录格式。
通用表空间(General Tablespaces)
通用表空间为通过create tablespace语法创建的共享表空间。通用表空间可以创建于mysql数据目录外的其他表空间,其可以容纳多张表,且其支持所有的行记录格式。
撤销表空间(Undo Tablespaces)
撤销表空间由一个或多个包含Undo日志文件组成。在MySQL 5.7版本之前Undo占用的是System Tablespace共享区,从5.7开始将Undo从System Tablespace分离了出来。InnoDB使用的undo表空间由innodb_undo_tablespaces配置选项控制,默认为0。参数值为0表示使用系统表空间ibdata1;大于0表示使用undo表空间undo_001、 undo_002等。
临时表空间(Temporary Tablespaces)
分为session temporary tablespaces 和global temporary tablespace两种。session temporary tablespaces 存储的是用户创建的临时表和磁盘内部的临时表。global temporary tablespace储存用户临时表的回滚段(rollback segments )。mysql服务器正常关闭或异常终止时,临时表空间将被移除,每次启动时会被重新创建。
7、InnoDB逻辑存储结构

Tablesapce
InnoDB存储引擎在存储设计上模仿了Oracle的存储结构,数据是按照表空间进行管理的。表空间,用于存储多个ibd数据文件,用于存储表的记录和索引。一个文件包含多个段。
Segment
段,用于管理多个Extent,分为数据段(Leaf node segment)、索引段(Non-leaf node segment)、回滚段(Rollback segment)。一个表至少会有两个段,一个管理数据(内节点段),一个管理索引(叶子段)。每多创建一个索引,会多两个段。
Extent
区,一个区固定包含64个连续的页,大小为1M。当表空间不足,需要分配新的页资源,不会一页一页分,直接分配一个区。
对一个区结构的描述XDES Entry(extent describe entey),哪个page的数据存满了,哪个page是空的。
Page
页,用于存储多个Row行记录,大小为16K。包含很多种页类型,比如数据页,undo页,系统页,事务数据页,大的BLOB对象页。
Row
行,包含了记录的字段值,事务ID(Trx id)、滚动指针(Roll pointer)、字段指针(Field pointers)等信息。
8、InnoDB行记录格式
我们平时插入的一行记录在磁盘上的存放方式也被称为行记录格式。InnoDB存储引擎目前有4种不同类型的行记录格式,分别是Compact、Redundant、Dynamic和Compressed行记录格式。
指定行记录格式的语法
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行记录格式名称
ALTER TABLE 表名 ROW_FORMAT=行记录格式名称复制
下面我介绍下各个行记录格式
Compact行记录格式

记录的额外信息
这部分信息是服务器为了描述这条记录而不得不额外添加的一些信息,这些额外信息分为3类,分别是变长字段长度列表、NULL值列表和记录头信息
变长字段长度列表
在Compact行记录格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序逆序存放。
NULL值列表
表中的某些列可能存储NULL值,如果把这些NULL值都放到记录的真实数据中存储会很占地方,所以Compact行记录格式把这些值为NULL的列统一管理起来,存储到NULL值列表中。
记录头信息
由固定的5个字节组成。5个字节也就是40个二进制位,不同的位代表不同的意思
这些二进制位代表的详细信息如下:
记录的真实数据
记录的真实数据除了列1、列2、列n这些我们自己定义的列的数据以外,MySQL会为每个记录默认的添加一些列(也称为隐藏列),具体的列如下:
Redundant行记录格式

Compact行记录格式的开头是变长字段长度列表,而Redundant行记录格式的开头是字段长度偏移列表,没有了NULL值列表
Redundant行记录格式的记录头信息占用6字节,48个二进制位
Dynamic和Compressed行记录格式
线上目前使用的MySQL版本是5.7,它的默认行记录格式就是Dynamic,这俩行记录格式和Compact行记录格式挺像,只不过在处理行溢出数据时有点不一样,它们不会在记录的真实数据处存储字段真实数据的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。
行溢出数据
VARCHAR(X)最多能存储的数据
对于VARCHAR(X)
类型的列最多可以占用65535
个字节,MySQL
对一条记录占用的最大存储空间是有限制的,除了BLOB
或者TEXT
类型的列之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535
个字节。
记录中的数据太多产生的溢出
MySQL
中磁盘和内存交互的基本单位是页
,也就是说MySQL
是以页
为基本单位来管理存储空间的,我们的记录都会被分配到某个页
中存储。而一个页的大小一般是16KB
,也就是16384
字节,而一个VARCHAR(X)
类型的列就最多可以存储65532
个字节,这样就可能造成一个页存放不了一条记录的情况。
真实数据处只会存储该列的前768
个字节的数据和一个指向其他页的地址,然后把剩下的数据存放到其他页中,这个过程也叫做行溢出
,存储超出768
字节的那些页面也被称为溢出页
。
9、InnoDB数据页结构
数据页代表的这块16KB
大小的存储空间可以被划分为多个部分,不同部分有不同的功能,各个部分如图所示:

InnoDB数据页的存储空间大致被划分成了
7
个部分File Header
文件头部,占38字节,页的一些通用信息。
这个页的编号是多少,它的上一个页、下一个页是谁。
Page Header
页面头部,占56字节,数据页专有的一些信息。
本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽,已删除记录占用的字节数,最后插入记录的位置等。
Infimum + Supremum
最小记录和最大记录,占26字节,两个虚拟的行记录。
User Records
用户记录,占用空间不确定,实际存储的行记录内容。
Free Space
空闲空间,占用空间不确定,页中尚未使用的空间。
Page Directory
页面目录,占用空间不确定,页中的某些记录的相对位置。
File Trailer
文件尾部,占用8字节,校验页是否完整。
每个记录的头信息中都有一个
next_record
属性,从而使页中的所有记录串联成一个单链表
。InnoDB
会把页中的记录划分为若干个组,每个组的最后一个记录的地址偏移量作为一个槽
,存放在Page Directory
中,所以在一个页中根据主键查找记录是非常快的,分为两步:通过二分法确定该记录所在的槽。通过记录的next_record属性遍历该槽所在的组中的各个记录。每个数据页的
File Header
部分都有上一个和下一个页的编号,所以所有的数据页会组成一个双链表
。为保证从内存中同步到磁盘的页的完整性,在页的首部和尾部都会存储页中数据的校验和和页面最后修改时对应的
LSN
值,如果首部和尾部的校验和和LSN
值校验不成功的话,就说明同步过程出现了问题。