共计 5570 个字符,预计需要花费 14 分钟才能阅读完成。
- 前言
- Instant add or drop column 的主线逻辑
- 表定义的列程序与 row 存储列程序论述
- 引入 row 版本的必要性
- 数据腐化问题
- 起因剖析
- Bug 重现与解析
- MySQL8.0.30 修复计划
前言
DDL 绝对于数据库的 DML 之类的其余操作,相对来说是比拟耗时、绝对重型的操作; 因而对业务的影比较严重。MySQL 从 5.6 版本开始始终在继续改良其 DDL 性能:引入了 online DDL,inplace DDL,instant DDL 等实用性极强的性能,DDL 目前对业务的影响继续升高。
MySQL 8.0.29 引入了 instant add/drop column 性能,反对在任意地位增加 column, drop column 也不须要表数据的任何模式的挪动,只须要批改表的元数据就能够实现 add/drop column,所以 instant add/drop column 的操作是轻型操作,速度快,资源需求量少。
ALTER table drop column a, ALGORITHM=INSTANT;
8.0.29 引入了新的 alter 算法 INSTANT。
然而这个新性能目前很不稳固,导致的问题比拟多; 而且通常都比较严重: 数据损坏,或者数据库无奈启动等。
本文是剖析其中的一个问题: 对表进行 instant drop 后,进行 update,之后数据库停机,而后数据库无奈启动。
为剖析这个问题,咱们会从 instant add/drop column 在 Innodb 的实现原理与细节方面来论述这个数据腐化 bug 的具体起因。
Instant add or drop column 的主线逻辑
因为这个性能的 WorkLog 无奈从官网获取,所以无奈失去精确的设计出发点,通过浏览相干代码,得出要实现这个性能,必须要解决以下关键点:
- 因为要反对在任意地位增加 / 删除列,同时不会更改表数据文件,所以表的逻辑定义与 row 的理论存储模式须要映射关系,不再是所见即所得的一一对应的关系。即为了实现这样性能:
-
- 表中列的定义程序与表中行数据 (row) 的存储程序是不同的。
- 同时对同一个 table 能够做屡次 instant DDL, 所以须要引入版本机制,在表的数据文件中, 不同 row 对应的表定义可能是不同的,须要在 row 中记住表定义的 version。
以上能够认为是该性能的设计准则与实现的主线逻辑。
表定义的列程序与 row 存储列程序论述
在引入这个性能之前,create table 时列定义的程序与列在 InnoDB 中存储的程序是统一的。(这里咱们不必思考 InnoDB 增加零碎暗藏列)
Instant add/drop column 要实现的亮点性能是在表定义的任意地位增加或者缩小 column,同时做这样的操作的时候,可能做到不须要重构表数据。
咱们称 column 在表定义中呈现的程序为 逻辑程序;
而 column 在行数据的存储程序为 物理程序。
要做到批改表定义,而不重构表数据,就必须将逻辑程序与物理程序解耦: 不能再像 MySQL 8.0.29 之前的版本那样,逻辑程序与物理程序是完全一致的;而从 8.0.29 开始通过表的元数据保留了逻辑程序与物理程序的映射关系。这种映射关系的构建与保护形成了 instant add/drop column 的根底.
如下图简略论述了逻辑 / 物理程序的关系。
引入 row 版本的必要性
对于同一张表,Instant add/drop DDL 能够执行屡次;每一次执行后,逻辑 / 物理程序的映射关系就发生变化;同时 instant add/drop DDL 并不需要做表数据的重构操作;因而能够得出通过屡次 instant add/drop DDL,InnoDB 存储的行数据与表定义存在多种逻辑 / 物理程序映射关系:比如说,在 ibd 文件中,前十行数据对应原始的表定义,接下来的十行可能对应着 instant add column 后的数据,再接下来的十行,可能对应着 instant drop column 后的数据。
为了治理这种模式的逻辑 / 物理,在 InnoDB 中,为每一行理论存储的数据引入了版本号的概念:每个版本号对应着一个逻辑 / 物理映射关系。
为存储这个版本信息,InnoDB 中,row 的信息头记录的格局有略微的变动:
如上图所示,在 row 的 extra 中存储了其对应的版本号,同时在 row header 中有标记位批示出了是否存在版本号信息。
依据版本号获取相应的映射关系,就能够正确的解析行数据。
目前版本号最大反对到 64,instant add/drop column 达到这个限度后报错;其后如果还须要 instant add/drop column DDL 操作,可能须要做一次可能触发 table rebuild 操作才能够。
数据腐化问题
由 instant add/drop column 引入了多个数据腐化问题,其中一个问题能够从:
[PS-8292] MySQL 8.0.29 fails to perform crash recovery – Percona JIRA(https://jira.percona.com/browse/PS-8292) 查看。
这个问题简略来说:在对表进行 instant drop 后,进行 update 操作,之后 MySQL server 重启,在启动阶段复原之前的 update 操作会引发 assert 解体(debug 版本的状况下)。
从代码上看,这个 bug 可能会造成数据的静默谬误(数据齐全错乱而且不报任何谬误),而不仅仅是解体这一种景象。
通过对 core 文件的简略剖析,造成该问题的大略起因如下:
在通过 redo 做复原的时候,字段的逻辑程序与物理存储程序之间的映射关系不对 (错位) 导致的。在复原期间可能会 找不到对应的字段 ,或者 更新了谬误的字段。
起因剖析
从原始的问题看,这个是产生在 InnoDB 启动复原阶段。这一阶段离不开 redo log 的参加。后面介绍 instant add/drop 设计要点的时候,那些列出的要点,能够认为是在在 DDL 期间的工作以及编码的根本逻辑;那么在实现 instant DDL 时候,在 DML 的时候也须要将必要的信息写入 redo log 能力做到 recovery。
- 为反对 instant add/drop column,redo log 记录的格局产生了变动,因为代码 bug,导致在解析 redo log 做复原的时候,失去的字段信息谬误,导致数据腐化。
- 问题体现进去可能是: 复原始终无奈执行,数据库无奈启动;还可能是复原到谬误的数据,数据库可能启动。
因为 redo log 的品种较多,信息也比拟繁冗,这里咱们只关注问题自身中呈现的 update 相干的 redo log,进而较多的关注 update redo log 与该问题相干的字段信息。
下图简要的论述了 update redo log 相干内容:
到这里,能够看到 在 MySQL 8.0.29 中,update redo log 引入了 instant column 的物理逻辑程序。
上面从 InnoDB 的复原流程跟踪问题产生的起因,其中次要须要关注的是复原过程中的表 (索引) 定义。
- 利用 redo log 是在数据库启动阶段最开始就执行,此时数据字典无奈关上,获取不到待复原表的定义信息
- 然而此时须要表的定义信息去解析 redo log 中的相干数据
- 此时就会依据 redo log 中记录的长度信息,以及记录长度的程序构建长期的表定义,此时仅仅是为了复原,并不需要准确的表定义,此时只须要晓得 field 的长度和地位即可。
- 同时如果 redo log 中如果有 instant DDL 的信息,那么也会用这些信息去批改长期构建的表定义: 这是问题产生的初始谬误的中央。
- 复原过程中,构建出的长期表实际上表中列的逻辑程序,这是合乎失常运行的需要的。
- 然而实际上 8.0.29 中字段长度的记录程序是按字段 (列) 的物理存储程序写入的。
- 如果带有 instant DDL 的信息,那么批改表定义时就会按物理程序去批改逻辑程序的表定义,这样会批改到非预期的字段,导致谬误产生!
Bug 重现与解析
CREATE TABLE `tb1` (`col1` VARCHAR(10) NOT NULL,
`col2` char(13),
`col3` varchar(11),
PRIMARY KEY (`col1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO tb1 VALUES ('4000','50','100');
--echo # the FIRST INSTANT ALTER
ALTER TABLE tb1 DROP COLUMN col2, LOCK=DEFAULT;
INSERT INTO tb1 VALUES('4545', '52');
UPDATE tb1 SET col3 = '46' WHERE col1 = '4545';
--echo # crash and restart 1
--source include/kill_and_restart_mysqld.inc
CHECK TABLE tb1;
DROP TABLE tb1;
以上 MySQL MTR 测例能够重现 InnoDB 启动复原期间始终 core 的问题。咱们从这个例子登程,联合下面解释的 instant drop DDL 代码行为看看问题是如何一步步产生的。
- 首先阐明一下,在测例运行期间逻辑程序与物理程序的变动。如下图所示略微展现了 table 的逻辑定于与 InnoDB row 存储的以下细节。这里留神的是 被 dropped column 依然会以暗藏列的模式存在于表定于中:因为 drop 之前存在的 row 还是须要这样信息解析字段。
-
联合 redo log 的复原过程看看问题产生的第一现场。这里针对这个测例摘取相干 redo log 的局部信息:
2.1 依照字段长度列表(8.0.29 中是物理程序写入的列表)创立的专门用于复原的表,相似于: create table dummy_table (d1:10, d2:13, d3:11)
2.2 依照 instant 字段信息批改 dummy 表:依照 physical pos=1 去批改后,后果相似于:create table dummy_table (d1:10, d2:13[dropped], d3:11)
2.3 冀望的正确的表应该相似于:create table dummy_table(d1:10, d3:11, d2:13[dropped]);
2.4 Redo log 中的 Field_no=1, 去复原期间望用到的是 #2.3 的表,然而过程中创立的是 #2.2 中谬误的表,这样当 Field_no= 1 去复原数据时,会谬误的发现对应的 field(column)曾经 dropped, 导致 core!
MySQL8.0.30 修复计划
晓得了问题产生的起因, 修复起来就比较简单了:
- MySQL 8.0.30 的代码修复计划
-
- Redo log 中字段的长度列表,依照字段的逻辑程序写入,不再按存储程序写入。
- 在 redo log 的 instant column 信息中也蕴含了字段的逻辑地位。
- Redo log 的记录自身的版本设置为 1,与 8.0.29 的版本为 0,做出差异。
- 8.0.30 的修复代码自身也是不能正确解析 8.0.29 产生的 redo log,只是依据版本号检测出 8.0.29 redo log,进而报错避免数据进一步好转。实际上 8.0.29 的 redo log,在 instant DDL 后,是不可能正确解析的,因为没有逻辑 / 物理的映射关系。
- 修复的逻辑比较简单:
-
-
Redo log 中字段的长度列表,依照字段的逻辑程序写入:
保障在复原阶段构建的长期表是按正确的逻辑定义程序构建的。
-
在 redo log 的 instant column 信息中也蕴含字段的逻辑地位:
保障在更新长期表的字段时,依照逻辑程序,不会呈现谬误更新的状况。
-
上面是 MySQL 8.0.30 update redo log 相干字段信息:
从上图能够看出,MySQL 8.0.30 redo log 中曾经不存储物理地位相干的信息了,全副是逻辑地位相干的信息;这样就和 MySQL 8.0.29 redo log 这种有问题的记录形式是过眼云烟了。
附带的这个测例能够重现数据的静默谬误(复原过程没问题,然而数据实际上错了)
CREATE TABLE `tb2` (`c1` char(4) NOT NULL, `c2` char(4), `c3` char(4), PRIMARY KEY (`c1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
begin;
INSERT INTO tb2 VALUES ('1000','2000','3000');
commit;
--echo # the FIRST INSTANT ALTER
ALTER TABLE tb2 add COLUMN c4 char(4) after c1, LOCK=DEFAULT;
INSERT INTO tb2 VALUES ('1001','4001', '2001', '3001');
SELECT * FROM tb2;
UPDATE tb2 set c4='4002' WHERE c1='1001';
--echo # crash and restart 1
--source include/kill_and_restart_mysqld.inc
select * from tb2;
CHECK TABLE tb2;
须要把这个测例放到 innodb test case suite 中。
Enjoy GreatSQL :)
## 对于 GreatSQL
GreatSQL 是由万里数据库保护的 MySQL 分支,专一于晋升 MGR 可靠性及性能,反对 InnoDB 并行查问个性,是实用于金融级利用的 MySQL 分支版本。
相干链接:GreatSQL 社区 Gitee GitHub Bilibili
GreatSQL 社区:
社区博客有奖征稿详情:https://greatsql.cn/thread-100-1-1.html
技术交换群:
微信:扫码增加
GreatSQL 社区助手
微信好友,发送验证信息加群
。