• 前言
  • 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 ALTERALTER 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.incCHECK TABLE tb1;DROP TABLE tb1;

以上MySQL MTR 测例能够重现 InnoDB 启动复原期间始终 core 的问题。咱们从这个例子登程,联合下面解释的 instant drop DDL 代码行为看看问题是如何一步步产生的。

  1. 首先阐明一下,在测例运行期间逻辑程序与物理程序的变动。 如下图所示略微展现了 table 的逻辑定于与 InnoDB row 存储的以下细节。这里留神的是 被 dropped column 依然会以暗藏列的模式存在于表定于中:因为 drop 之前存在的 row 还是须要这样信息解析字段。

  1. 联合 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 ALTERALTER 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.incselect * 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社区助手微信好友,发送验证信息加群