【译】MySQL InnoDB 排序索引构建
前言
不必了解 MySQL®
和Percona Server for MySQL
如何构建索引。 但是,如果您了解处理过程,那么当您想为数据插入保留适当数量的空间时,它可能会有所帮助。 从 MySQL 5.7 开始,开发人员改变了他们为 InnoDB 构建二级索引的方式,应用了自底向上而不是早期版本中使用的自顶向下方法。 在这篇文章中,我将通过一个示例来说明如何构建 InnoDB
索引。 最后,我将解释如何使用这种理解来为 innodb_fill_factor
设置一个合适的值。
索引构建过程
要在现有数据的表上建立索引,在 InnoDB 中有以下阶段:
- 读取阶段(从聚集索引读取并构建二级索引条目)
- 合并排序阶段
- 插入阶段(将排序后的记录插入二级索引)
在 5.6 版本之前,MySQL 通过一次插入一条记录来构建二级索引。这是一种“自上而下”的方法。对插入位置的搜索从根(顶部)开始,并到达相应的叶页(向下)。记录插入到光标指向的叶页上。在查找插入位置以及进行页面拆分和合并(在根级别和非根级别)方面,成本很高。你怎么知道发生了太多的页面拆分和合并?您可以在我的同事 Marco Tusa 较早的博客中了解这一点,请点击此处。
从 MySQL 5.7 开始,添加索引期间的插入阶段使用“排序索引构建”,也称为“批量加载索引”。在这种方法中,索引是“自下而上”构建的。即叶页面(底部)首先构建,然后非叶级别直到根。
用例
在这些情况下使用排序索引构建:
* ALTER TABLE t1 ADD INDEX (or CREATE INDEX)
* ALTER TABLE t1 ADD FULLTEXT INDEX
* ALTER TABLE t1 ADD COLUMN, ALGORITHM=INPLACE
* OPTIMIZE TABLE t1
对于最后两个用例,ALTER
创建一个中间表。中间表索引(主键索引和二级索引)是使用“排序索引构建”构建的。
算法
- 在 Level 0创建一个页面。同时创建一个指向该页面的游标。
- 使用 Level 0 的游标插入数据到页面,直到填满。
- 一旦页面已满,创建一个兄弟页面(不插入数据到兄弟页面)。
- 为当前满页创建一个节点指针(子页中的最小键),并在上一层(父页)插入一个节点指针。
- 在上层,检查游标是否已经定位。如果没有,请为该级别创建一个父页面和一个游标。
- 在父页面插入节点指针。
- 如果父页面已满,请重复步骤 3、4、5、6。
- 现在插入兄弟页面并使游标指向兄弟页面。
- 在所有插入的末尾,每个级别都有一个指向最右侧页面的游标。提交所有游标(意味着提交修改页面的小事务,释放所有锁存器)。
为简单起见,上述算法跳过了有关压缩页面和处理 BLOB(外部存储的 BLOB)的细节。
自下而上构建索引的演示
使用一个示例,让我们看看如何构建二级索引,自下而上。再次为简单起见,假设叶页和非叶页中允许的最大记录数为 3。
CREATE TABLE t1 (a INT PRIMARY KEY, b INT, c BLOB);
INSERT INTO t1 VALUES (1, 11, 'hello111');
INSERT INTO t1 VALUES (2, 22, 'hello222');
INSERT INTO t1 VALUES (3, 33, 'hello333');
INSERT INTO t1 VALUES (4, 44, 'hello444');
INSERT INTO t1 VALUES (5, 55, 'hello555');
INSERT INTO t1 VALUES (6, 66, 'hello666');
INSERT INTO t1 VALUES (7, 77, 'hello777');
INSERT INTO t1 VALUES (8, 88, 'hello888');
INSERT INTO t1 VALUES (9, 99, 'hello999');
INSERT INTO t1 VALUES (10, 1010, 'hello101010');
ALTER TABLE t1 ADD INDEX k1(b);
InnoDB 将主键字段附加到二级索引。二级索引 k1 的记录格式为 (b, a)。在排序阶段之后,记录是:
(11,1), (22,2), (33,3), (44,4), (55,5), (66,6), (77,7), (88,8), (99,9), (1010, 10)。
初始插入阶段
让我们从记录 (11,1) 开始。
- 在 Level 0(叶级别)创建一个页面。
- 在页面上创建一个游标。
- 所有插入内容都会转到此页面,直到填满为止。
![](https://img.haomeiwen.com/i3111343/87f54f40c465b3b3.png)
箭头显示游标当前指向的位置。它目前在第 5 页,下一个数据插入在此页。
还有两个空槽,因此插入记录 (22,2) 和 (33,3) 很简单。
![](https://img.haomeiwen.com/i3111343/dc7e7785914f2c27.png)
对于下一条记录 (44, 4),第 5 页已满。以下是步骤。
页面填满时的索引构建
- 创建兄弟页面 – 第 6 页。
- 暂时不要插入数据到兄弟页面。
- 提交游标处的页面,即迷你事务提交、释放闩锁等。
- 作为提交的一部分,创建一个节点指针并将其插入到父页面(current Level(0) + 1) 中。即在 Level 1。
- 节点指针的格式为(子页中的最小键,子页号)。第 5 页的最小键是 (11,1)。在父级插入记录 ((11,1),5)。
- Level 1 的父页面尚不存在。 MySQL 创建第 7 页和指向第 7 页的游标。
- 将 ((11,1),5) 插入第 7 页。
- 现在,返回到 Level 0 并创建从第 5 页到第 6 页的链接,反之亦然。
- Level 0 的游标现在指向兄弟页第 6 页。
- 将 (44,4) 插入第 6 页。
![](https://img.haomeiwen.com/i3111343/756c25da0e14693e.png)
下一个插入 - (55,5) 和 (66,6) - 很简单,在第 6 页。
![](https://img.haomeiwen.com/i3111343/f12e0c34982ed90a.png)
记录 (77,7) 的插入与 (44,4) 类似,只是父页面(第 7 页)已经存在并且它有空间容纳另外两条记录。先将节点指针 ((44,4),6) 插入第 7 页,然后将 (77,7) 记录到同级第 8 页中。
![](https://img.haomeiwen.com/i3111343/caa582869ddcb2d4.png)
插入记录 (88,8) 和 (99,9) 很简单,因为第 8 页有两个空槽。
![](https://img.haomeiwen.com/i3111343/dbcbbebeac96bf97.png)
下一个插入 (1010, 10)。将节点指针 ((77,7),8) 插入到级别 1 的父页面(第 7 页)。
MySQL 在 Level 0 创建兄弟页 9。将记录 (1010,10) 插入第 9 页并将游标更改为该页。
![](https://img.haomeiwen.com/i3111343/5ba639c282e2fd01.png)
提交所有级别的游标。在上面的示例中,数据库在级别 0 提交第 9 页,在级别 1 提交第 7 页。我们现在有一个自下而上构建的完整 B+-树索引!
索引填充因子
全局变量 innodb_fill_factor
设置 Btree
索引页面中用于插入的空间量。默认值为 100,表示使用整个页面(不包括页眉、页尾)。聚集索引具有 innodb_fill_factor = 100
的豁免。在这种情况下,1/16 的聚集索引页面空间保持空闲。 也就是说。 6.25% 的空间是为未来的 DML
保留的。
innodb_fill_factor
值 80 意味着 MySQL
使用 80% 的页面用于插入,并留下 20% 用于将来的更新。
如果 innodb_fill_factor 为 100,则没有剩余空间可用于将来插入二级索引。如果您希望在添加索引后表上有更多的 DML,DML 可能会导致页面拆分以及再次合并。在这种情况下,建议使用 80-90 之间的值。此变量值还会影响OPTIMIZE TABLE
或ALTER TABLE DROP COLUMN、ALGORITHM=INPLACE
索引语句的索引重建。
您不应该使用太低的值——例如低于 50——因为索引会占用更多的磁盘空间。值越低,索引中的页面就越多,索引统计信息采样可能不是最佳的。优化器可能会选择具有次优统计信息的错误查询计划。
排序索引构建的优势
- 没有页面拆分(不包括压缩表)和合并。
- 没有重复查找插入位置(页面反复读取,IO较多)。
- 插入不会记录 redo 日志(页面分配除外),因此重做日志子系统的压力较小。
缺点
没有……嗯,好的,有一个,值得单独发表一篇博文🙂敬请关注!