最近遇到一个BUG,影响为8029-8039,如果有使用这些版本需要注意这个BUG,8040修复,如果新上8.0至少考虑8040之后的版本。
Bug#36723117: Crash using a secondary index after dropping a column with ALGORITHM=INSTANT
下面是构造方式、规避方式、大概原因。
一、crash模拟
在8030-8039都会触发crash,构造方式来自于
INSTANT DDL发现bug(1)- instant drop的表在delete中crash https://zhuanlan.zhihu.com/p/6735089834?open_in_browser=true
里面提到例子的扩展,这里需要在非debug版本触发,并且也要测试add字段的影响。
drop table t1;
CREATE TABLE `t1` (
`a` int,
`a1` int,
`a2` int,
`b` int,
`c` int,
`d` int,
`e` int,
`f` int,
KEY `key_1` (`b`,`c`,`d`, `e`,`f`)
);
alter table t1 add column abug int after a,algorithm = instant;
insert into t1 values(1,1,1,1,0,1, 1, 1,1);
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
insert into t1 values(1,1,1,1,1, 1, 1, 1,1);
START TRANSACTION;
DELETE FROM t1 where b=1;
UPDATE t1 SET e = 1 WHERE b = 1;
最后一句update会crash
二、触发必要条件和规避方式
简单描述这个BUG的必要条件 1、索引字段个数要大于3个 2、要使用drop和after中间字段,并且要用instant算法
规避方式可以考虑为: 1、最好用inplace算法来避免这个问题 2、升级8.0.40及以后 3、控制二级索引个数不要超过3个 4、不要drop中间字段和使用after语法增加字段。
三、大概原理
我们在29版本过后加入了instant 能够支持drop和after add字段,而行的物理格式还是原始顺序,但是字典顺序就是按照表定义给出来的,举个我们上面的例子
drop table t1;
CREATE TABLE `t1` (
`a` int,
`a1` int,
`a2` int,
`b` int,
`c` int,
`d` int,
`e` int,
`f` int,
KEY `key_1` (`b`,`c`,`d`, `e`,`f`)
);
alter table t1 add column abug int after a,algorithm = instant;
从主键(只有主键索引才会受到影响)的物理顺序来讲字段abug字段的物理顺序在f字段之后,而字段顺序abug字段就在a字段之后,那么要正确的解析行就需要额外的加入很多信息,从变更来看至少有
行头加入REC_INFO_VERSION_FLAG标记,用于判断是否是有instant算法后加入的行 行头加入version 用于经过了多少次instant算法变更 dict_index_t::fields_array 字段物理顺序到逻辑顺序的映射 dict_index_t::row_versions 本索引是否经过instants算法更改列信息 dict_index_t::nullables[MAX_ROW_VERSION + 1] null字段每个version版本的变化 dict_col_t::phy_pos 本字段的物理位置 dict_col_t::version_added 本字段删除的版本 dict_col_t::version_dropped 本字段加入的版本
可能还有当前就看到了这些。我们看到这里有了字段逻辑顺序到物理顺序的映射关系的表示存储在字典中,其次我们主键行一定包含3个列:
col0 DB_ROW_ID(primary key) col1 DB_TRX_ID col2 DB_ROLL_PTR
这3个字段顺序一定是不会变化的。重点是问题代码部分,某些情况下实际上传入的offsets和field_no都是二级索引的,但是rec_index是主键,通过主键去解析二级索引的逻辑field_no,返回的实际上是主键的逻辑字段对应的物理字段位置,然后去比对这个物理字段位置是否大于了二级索引的字段总数,如下,
这个就给BUG创造条件。offsets的解析大概如下,
sup/infi 伪列
offsets[0] 为 数组元素个数,字段+1+ (2 or 4(debug版本))
offsets[1] 为字段个数
如果为debug版本偏移量顺延2
offsets[2] 为 REC_N_NEW_EXTRA_BYTES(5) 和 32bytes 高位设置为REC_OFFS_COMPACT(31) 1
offsets[3] 为8
row数据行
offsets[0] 为 数组元素个数,字段+1+ (2 or 4(debug版本))
offsets[1] 为字段个数
如果为debug版本偏移量顺延2
offsets[2] 保留
offsets[3] 字段1长度 主键为ROW_ID或者主键字段 比如为6
offsets[4] 字段2长度 主键为DB_TRX_ID 比如为6+6=12
offsets[5] 字段3长度 主键为DB_ROLL_PTR 比如6+6+7=19
offsets[6] 字段3长度 id字段(int) 比如6+6+7+4=23
我们可以看到在行数据的offsets中存在一个offsets[1]是字段的总量,问题代码将二级索引的逻辑field_no转换为主键的物理字段顺序,我们的例子中,主键的字段逻辑顺序是,
col[0] DB_ROW_ID | col[1] DB_TRX_ID |col[2] DB_ROLL_PTR |col[3] a |col[4] abug|col[5] a1|col[6] a2|col[7] b|col[8] c|col[9] d|col[10] e|col[11] f
表主键的物理顺序可以在debug版本中找到如下,
SET SESSION DEBUG = '+d,skip_dd_table_access_check';
mysql> SELECT name,se_private_data FROM mysql.columns WHERE table_id = (SELECT ID FROM mysql.tables WHERE NAME = 't1' and schema_id=28) ;
+-------------+---------------------------------------------------------------+
| name | se_private_data |
+-------------+---------------------------------------------------------------+
| a | physical_pos=3;table_id=2325; |
| a1 | physical_pos=4;table_id=2325; |
| a2 | physical_pos=5;table_id=2325; |
| abug | default_null=1;physical_pos=11;table_id=2325;version_added=1; | -----注意这里
| b | physical_pos=6;table_id=2325; |
| c | physical_pos=7;table_id=2325; |
| d | physical_pos=8;table_id=2325; |
| DB_ROLL_PTR | physical_pos=2;table_id=2325; |
| DB_ROW_ID | physical_pos=0;table_id=2325; |
| DB_TRX_ID | physical_pos=1;table_id=2325; |
| e | physical_pos=9;table_id=2325; |
| f | physical_pos=10;table_id=2325; |
+-------------+---------------------------------------------------------------+
注意这里的physical_pos这里col[4] abug 对应的物理顺序是11,因为索引(b
,c
,d
, e
,f
)存在5个字段也就是col[4]是f字段,这个时候传入二级索引的field_no为4,得到的主键 关于这个字段的物理位置为11,如下图所示箭头所表示的,这也是为什么要构造abug字段的原因,
主键 二级索引
(物理位置0) col[0] DB_ROW_ID col[0] b
(物理位置1) col[1] DB_TRX_ID col[1] c
(物理位置2) col[2] DB_ROLL_PTR col[2] d
(物理位置3) col[3] a col[3] e
(物理位置11)col[4] abug <----------- col[4] f
(物理位置4) col[5] a1
然后检测11是否大于offsets中二级索引的总字段个数,大于则crash,这里二级锁索引也才5个字段肯定11比5要大得多,这个也可以在debug中看到,如下,
这里b,c,d,e 字段不会有这个问题因为它们的字段逻辑顺序和物理顺序在主键是一样的,并且b,c,d对应的DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR是不可能更改的,因此3个字段的二级索引应该没有问题,其次不用instant算法改变物理和逻辑顺序也不会遇到。
四、参考文章
https://zhuanlan.zhihu.com/p/946510015 https://zhuanlan.zhihu.com/p/6735089834?open_in_browser=true




