MySQL存储引擎
数据库引擎是数据库用于存储、处理和保护数据的核心服务,不同的数据库引擎有其各自的特点,如存储机制、索引技巧、主键的处理、锁的粒度等特点便随着引擎的不同而变化。不同的数据文件在磁盘的组织形式。

常用存储引擎
介绍存储引擎之前先介绍两个基本概念:局部性原理和磁盘预读。
- 局部性原理
- 空间局部性:当一个数据被使用后,其附近的数据也会大概率被使用。
- 时间局部性:当运行某段程序或者读取某段数据时,在一段时间内很可能被再次运行或读取。
- 磁盘预读:磁盘最小的逻辑单位是扇区,一般来说是512个字节,而文件的最小逻辑单位是页通常是4kb,而InnoDB最小的操作单位是页通常是16kb。系统读取数据时一定是最小逻辑单位的整数倍,因此当我们只需要读取一字节的数据时,会同时把整个单位的数据读取到内存中,这样预读出的数据则与局部性原理相对应,方便之后的使用。
InnoDB
InnoDB 支持ACID,行锁和外键,并且是MySQL的默认存储引擎。
InnoDB 主要通过锁和MVCC来支持高并发。其默认级别是REPEATABLE READ(可重复读),并且通过间隙锁(next-key locking)策略防止幻读的出现。
InnoDB 表是基于聚簇索引建立的,不过它的二级索引(secondary index,非主键索引)中必须包含主键列,所以如果主键很大的话,其他的所有索引都会很大。因此,若表上的索引较多的话,主键应当尽可能的小。
InnoDB 采用B+树存储数据,为什么选择B+树作为数据库的存储结构?
- AVL树、红黑树:每个节点上仅仅存储了单一节点,会导致整个树的深度较大,IO也因此增多
- Hash:
- 难以设计散列度高的哈希函数
- 虽然等值查询较快,但对于范围查询则难以查询
- B树:虽然每个节点能够存储多个值,但由于节点中直接存储了记录,因此单个节点所能够记录的数据有限。
- B+树,叶子节点存储真实数据,而非叶子节点仅仅存储索引值,因此每个节点通常能够记录更多的索引,降低了树的高度。
聚簇索引的优缺点:
- 数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快。
- 聚簇索引对于主键的排序查找和范围查找速度非常快。
- 插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键。
- 更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。
- 二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据
逻辑存储结构
InnoDB存储引擎中,表都是根据主键顺序组织存放的,这种存放方式的表称为索引组织表。如果没有主键则通过以下方式:
- 判断表中是否有非空的唯一键,有,则以该列为主键。若有多个非空唯一键,则以第一个定义的为主键
- InnoDB引擎自动创建一个6字节大小的rowid作为主键。rowid的分配是全局的,所有的表都共享这个ID,并不是每个行记录都有rowid。
InnoDB的逻辑存储结构包含:
- 表空间:
- 系统表空间:ibdata1,每个数据库都只有一个系统表空间。
- 独立表空间:.idb文件
- 段
- 区:一个区包含了64个页,正好是1MB
- 页: 页是InnoDB磁盘管理的最小单位,默认每个页的大小为16KB
- 行

页的存储格式
InnoDB页的存储结构:

将上面7个内容分成以下3个部分:
第一部分:

File Header: 用于描述页的各种信息

其中,检验和是为了使用两个较小的值来代表很长的字符串,通过比较检验和来判断两个字符串是否相等。当InnoDB将数据刷到磁盘时,由于一次刷一页即16KB而操作系统一般一页是4KB,可能会出现只刷了一半的情况。此时可以通过文件头的检验和和文件尾的检验和做对比就可以判断页是否完整的写入到磁盘上。(检验和分别位于File Header和File Trailer中)
第二部分:
- Free Space(空闲空间):每当我们插入语一条记录,都会从Free Space部分申请一个和记录大小的空间到User Space。当Free Space用完后就需要申请新的页。
- User Records(用户记录):记录按照指定的行格式存储在User Space中,相互之间形成了单链表。
- Infimum + Supermum(最小最大记录)
Compact行格式:

其中c1为主键(隐藏列少了row_id)

delete mask 表示当前记录是否被删除了,真实的底层存储时,记录时紧密在一起的,若直接将数据删除需要将后面的记录向前移。所有被删除的记录会组成一个垃圾链表,其中的空间是可重用的,如果有新的数据来可以直接把这些数据覆盖。
heap_no 表示当前记录在本页中的位置,其中0和1用于存放伪记录,一个代表最小记录,一个代表最大记录。由于他们是0和1因此在页的记录中位置最靠前。
第三部分:
Page Directory(页目录):由于是链表因此查找很慢,通过页目录能够实现二分查找。具体是用过将记录进行分组(一般4-8个一组,这种组称为slot),将每组最大的值取出,按每组最大的进行排序。其中第一组,只包含最小记录。最后一组包含最大记录,通常1-8条记录,其余的组一般4-8条记录。好处是除了第一组以外其余组的记录尽量相等。
每一组中最后一条记录的头部信息n_owner会存储该组有多少条记录。

Page Header(页面头部)

行格式
上面提及到了compact格式的记录,实际上不止这一种,我们可以通过下面操作来查看某张表所采用的记录格式。

行格式主要包含以下几种:
- compact行格式
- 变长字段记录长度列表:对于varchar类型,例如varchar(10),我们实际上可能只存了6个字符的数据,因此需要记录每个字符的长度,实际上是按照字段声明顺序的逆序存储的。


- NULL值列表:把所有可以为NULL的列统一管理。原因是数据需要对其,如果没有标注出来,查询数据时可能会出现混乱,而如果使用标记又太浪费空间。使用二进制表示1为NULL,0不为NULL。

- 记录头信息
- 记录的真实数据:3个隐藏列db_row_id、db_trx_id、db_roll_ptr
- 变长字段记录长度列表:对于varchar类型,例如varchar(10),我们实际上可能只存了6个字符的数据,因此需要记录每个字符的长度,实际上是按照字段声明顺序的逆序存储的。
- dynamic和compressed行格式
- 行溢出:varchar类型的列最多可以存储65535个字节,而InnoDB一个页一般是16KB,这样可能会出现一个页放不了一条记录,这种现象称为行溢出。在Compact和Redundant行格式中,对占用存储空间非常大的列,只会记录该列的一部分数据,将剩余的数据分散存储在其他的页中进行分页存储。

而对于dynamic和compressed,是直接将该字段全部放在溢出页当中,并不存储部分数据。同时compressed还会采用zlib算法对数据进行压缩。

- 行溢出:varchar类型的列最多可以存储65535个字节,而InnoDB一个页一般是16KB,这样可能会出现一个页放不了一条记录,这种现象称为行溢出。在Compact和Redundant行格式中,对占用存储空间非常大的列,只会记录该列的一部分数据,将剩余的数据分散存储在其他的页中进行分页存储。
- redundant行格式(MySQL5.0之前的存储方式)
MyIsam
MyIsam 使用非聚簇索引
- 不支持事务
- 不支持外键
- 表级锁定,读写互相阻塞:读阻塞写,不会阻塞读。而写锁则会把读写都阻塞。
- 只缓存索引,不缓存数据:缓存在内存的是索引,不是数据。MyIsam的索引是压缩的,可以更好的利用内存,而InnoDB缓存在内存的是数据,相对来说,服务器内存越大,InnoDB发挥的优势越大。
- 读取速度较快
Memory
Memory 采用的逻辑存储介质是系统内存,支持哈希和B树索引。虽然在内存中存储表数据确实会提供很高的性能,但当mysqld守护进程崩溃时,所有的Memory数据都会丢失。
| InnoDB | MyIsam | Memory | |
|---|---|---|---|
| 事务 | √ | × | × |
| 全文索引 | √ | √ | √ |
| 树索引 | √ | √ | √ |
| 哈希索引 | × | × | √ |
| 数据缓存 | √ | × | N/A |
| 外键 | √ | × | × |
小白制作,如有错误欢迎指正。

浙公网安备 33010602011771号