前言
大家好,我是xicheng。从这篇文章开始,会陆续地更新MySQL相关的相关文章。帮助大家提升基础的同时,顺便就准备了面试的八股文,开始发车。
常见存储引擎
在讲InnoDB之前先看一下MySQL有哪些常见的存储引擎。
InnoDB:支持事务,行锁设计,支持外键,通过MVCC获取高并发性,5.5.8开始成为MySQL的默认存储引擎。提供插入缓存,二次写,自适应哈希索引,预读等高性能,高可用功能。主要面向OLTP(在线事务处理)应用。表都是根据主键顺序组织存放的,这种方式叫索引组织表。
MyISAM:不支持事务,存储引擎表由MYD与MYI组成,前者存储数据文件,后者存储索引文件。主要面向OLAP(联机分析处理)应用。
Memory:数据放内存中,速度非常快,只支持表锁,并发性能差,索引默认使用哈希索引。
Archive:只支持SELECT与INSERT。适合存储归档数据。
NDB:集群存储引擎,数据全放内存中,JOIN操作是在Server层完成而不是存储引擎层。
Federated:不存放数据,指向远程数据库上的表。只支持MySQL数据库表,不支持其它数据库的表。
选择合适的存储引擎
需要支持事务,热备份,快速的崩溃恢复就选择InnoDB。
不需要支持事务,基本只有INSERT与SELECT,例如日志表。可以选择MyISAM。
行格式
【InnoDB-记录行】包括COMPACT,REDUNDANT,DYNAMIC(MySQL8.0默认格式),COMPRESSED
InnoDB1.0.x版本的文件格式定义为Barracuda,之前的版本定义为Antelope。Barracuda文件格式包含了Antelope文件格式,并引入了DYNAMIC,COMPRESSED两种行格式。如下图所示。
文章图片
COMPACT
行结构示意图
文章图片
- 变长字段长度列表
- 存的是各变长字段的真实数据占用的字节数。并按字段顺序逆序存放。
- 当maxlen (可变字段一个字符所需的字节数) * m(该字段最多存储的字符数) > 255,且L(真实存的字节数)> 127时,变长字段使用2字节记录长度。否则使用一个字节记录长度。
- NULL值列表
- 每列占用一个二进制位,按照字段顺序逆序排序,值为1表示该列的值为NULL。值为0表示该列的值不为NULL。
- NULL值列表必须用整数字节表示,若使用的二进制位数不足整数字节时,则在高位补0。
- 记录头信息,固定由5个字节组成,如下表所示。
名称 | 大小(位) | 描述 | |
---|---|---|---|
预留位1 | 1 | 没有使用 | |
预留位 | 21 | 没有使用 | |
deleted_flag | 1 | 标识该条记录是否被删除 | |
min_rec_flag | 1 | B+树中每层非叶子节点的最小的目录项都会添加该标记 | |
n_owned | 4 | 1页记录会被分为若干组,每组中有一条记录的该值代表该组中所有记录的条数。其余记录的该值为0 | |
heap_no | 13 | 当前记录在页面堆中的相对位置 | |
record_type | 3 | 0:普通记录,1:B+树非叶子节点的目录条数,2:Infimum记录(下边界,记录比该页中任何主键值都要小的值),3:Supremum记录(上边界,记录比该页中任何主键值都要大的值,构成了页中记录的边界) | |
next_record | 16 | 从当前记录的真实数据到下?条记录的真实数据的地址偏移量。这样向左读取就是记录头信息,向右读取就是真实数据。 |
除了有用户定义的列外,还有若干隐藏列,如下表所示。
列名 | 是否必须 | 大小(字节) | 描述 |
---|---|---|---|
DB_ROW_ID | 否 | 6 | 行id。如果?户没有定义主键,则取?个Unique键作为主键,如果表中无Unique键,InnoDB会默认添加?个DB_ROW_ID隐藏列作为主键 |
DB_TRX_ID | 是 | 6 | 事务idDB_ROLL_PTR是7回滚指针 |
DB_ROLL_PTR | 是 | 7 | 回滚指针 |
CHAR存储格式
当采用定长编码的字符集时,该列用的字节数不会被加到变长字段长度列表中。采用变长编码的字符集时,则会加到其中。
采用变长编码字符集时,CHAR(M)至少会占用M个字节。
REDUNDANT 行结构示意图
文章图片
行记录的额外信息
- 字段长度偏移列表
按照相邻两个偏移量的差值来计算各个列值的长度。并按字段顺序逆序存放。
比如,某一行的字段长度偏移列表为 18 15 0D 07 ,因为是逆序存放的,则按列排序就是 07 0D 15 18。
- 第1列长度就是0x07个字节,即7个字节。
- 第2列长度就是(0x0D - 0x07)个字节,即6个字节。
- 第3列长度就是(0x15 - 0x0D)个字节,即8个字节。
- 第3列长度就是(0x18 - 0x15)个字节,即3个字节。
- 记录头信息,固定由6个字节组成,如下表所示。
名称 | 大小(位) | 描述 | |
---|---|---|---|
预留位1 | 1 | 没有使用 | |
预留位2 | 1 | 没有使用 | |
deleted_flag | 1 | 标识该条记录是否被删除 | |
min_rec_flag | 1 | B+树中每层非叶子节点的最小的目录项都会添加该标记 | |
n_owned | 4 | 1页记录会被分为若干组,每组中有一条记录的该值代表该组中所有记录的条数。其余记录的该值为0 | |
heap_no | 13 | 当前记录在页面堆中的相对位置 | |
n_field | 10 | 记录中列的数量 | |
byte_offs_flag | 1 | 标记字段长度偏移列表中每个列对应的偏移量使用一个字节还是两个字节来表示 真实数据占用空间大小为n 当n<=127时,值为1,用1个字节表示偏移量 当127 |
|
next_record | 16 | 从当前记录的真实数据到下?条记录的真实数据的地址偏移量。这样向左读取就是记录头信息,向右读取就是真实数据 |
- NULL值处理
如果列对应的偏移量值的第1个比特位为1,则该列值就是NULL,否则就不是NULL。
如果存储NULL值的字段是定长类型,如CHAR(M),则字段对应的真实数据会使用0填充。
如果存储NULL值的字段是变长类型,如VARCHAR(M),则不在行记录的真实部分占用任何的存储空间。
CHAR存储格式
CHAR(M)占用空间 = 该字符集表示一个字符最多需要的字节数 * M。例如,utf-8的CHAR(10)占用30个字节。gbk的CHAR(10)占用20个字节。
文章图片
每页额外需要132个字节存储其它数据。每条记录需要固定用27个字节存储存储额外信息。
每页至少需要存2条记录。则有如下不等式:132 + 2 * (27 + n) < 16KB。解出n < 8099。当这列中存储的数据占用空间>=8099字节时,就会成为溢出列。
DYNAMIC 与COMPACT类似,区别是,处理溢出列时,会把该列的所有数据都存到溢出列中。
COMPRESSED 与DYNAMIC类似,区别是,会采用压缩算法对页面进行压缩。
结尾 MySQL通过InnoDB的记录行开了个头,希望大家能持续关注下去。下一篇MySQL文章讲InnoDB-数据页的存储结构。
微信扫描下方二维码,或搜索“xicheng”,关注公众号后回复【笔记】,有我准备的15万字Java面试笔记。
文章图片
感谢各位人才的点赞、收藏和评论,干货文章持续更新中,下篇文章再见!