MySQL 8.0 instant add/drop column 性能回退问题

issue 地址: https://bugs.mysql.com/bug.php?id=111538

影响范围: 从 8.0.29 版本开始, 在read heavy 场景, 性能可能有 5%~10% 的性能回退


MySQL 官方在8.0.29 里面加了instant add/drop column 能力, 能够实现 instant add 或者 drop cloumn 到表的任意位置. PolarDB 在这基础上增加了可以 Instant 修改列的能力, 具体可以看我们的月报

官方的实现介绍:

https://dev.mysql.com/blog-archive/mysql-8-0-instant-add-and-drop-columns/


instant DDL 核心观点只有一个: don't touch any row but update the metadata only, 也就是仅仅去修改 Data Dictionary(DD) 信息, 而不去修改数据信息,这样才有可能做到 Instant.

具体的做法就是给每一个行增加了row_version, 然后DD 本身就是多版本, 不同的数据信息用不同的DD 信息去解析.


首先一个record 是否有row_version 信息添加到了Record info bits 里面.

info bits 包含有deleted flag, min record 等等信息, 后来在8.0.13 的时候增加record 是否有Instant ADD column 信息. 在 8.0.29 版本中增加了record 是否有 row_version 信息.





以上是这个 issue 背景, Instant add/drop column 的原理, 但是原因在哪里呢?


从Markus 提交上来的Flamegraph 可以看到, 在 8.0.33 里面 rec_get_offsets/cmp_dtuple_rec/rec_get_nth_field 等等相比 8.0.28 占比明显增多了. 整个 row_serch_mvcc 的调用开销也增加了.







核心原因由于数据record 增加了 row_version 信息, 导致在执行数据解析的函数 rec_get_offsets/rec_get_nth_field 等函数中增加了很多额外的判断, 并且官方把很多 inline function 改成了 non-inline.

为了验证想法, 我们做了 3 个地方的修改, 具体可以看 Issue 上面的代码提交:

1. 将一些 non-inline function 改回inline function

从 inline => non-inline. 修改的函数如下:

8.0.27

rec_get_nth_field => inline

rec_get_nth_field_offs => inline

rec_init_offsets_comp_ordinary => inline

rec_offs_nth_extern => inline

8.2.0

rec_get_nth_field => non-inline

rec_get_nth_field_offs => non-inline

rec_init_offsets_comp_ordinary => non-inline

rec_offs_nth_extern => non-inline


我们测试下来在 oltp_read_only 场景里面, 将这些 non-inline 函数改成 inline 以后, 性能可以有 3~5% 左右的提升空间. 具体改动代码可以在 issue 里面获得.


2. 简化get_rec_insert_state 逻辑

8.0.29 增加了 get_rec_insert_state 函数, 需要判断当前 record 是来自哪一个版本升级上来的, 从而使用合适的 DD 代码逻辑进行解析. 如果是包含有 row_version 版本, 还需要判断是否带有 version 信息, 如果没有 version 信息, 是不是8.0.12 instant add column 版本等等, 这里的逻辑非常琐碎.

所以 REC_INSERT_STATE 的状态非常多.

enum REC_INSERT_STATE { /* Record was inserted before first instant add done in the earlier implementation. */ INSERTED_BEFORE_INSTANT_ADD_OLD_IMPLEMENTATION, /* Record was inserted after first instant add done in the earlier implementation. */ INSERTED_AFTER_INSTANT_ADD_OLD_IMPLEMENTATION, /* Record was inserted after upgrade but before first instant add done in the new implementation. */ INSERTED_AFTER_UPGRADE_BEFORE_INSTANT_ADD_NEW_IMPLEMENTATION, /* Record was inserted before first instant add/drop done in the new implementation. */ INSERTED_BEFORE_INSTANT_ADD_NEW_IMPLEMENTATION, /* Record was inserted after first instant add/drop done in the new implementation. */ INSERTED_AFTER_INSTANT_ADD_NEW_IMPLEMENTATION, /* Record belongs to table with no verison no instant */ // 如果index 上面没有做过instant add 或者 最新的row_version 版本Instant add/drop INSERTED_INTO_TABLE_WITH_NO_INSTANT_NO_VERSION, NONE };