189 8069 5689

回馈开源,我如何排查一个MySQLBug

X-Engine是阿里巴巴自研的高性能低成本存储引擎,经过多年的努力,我们在集团内部以AliSQL(X-Engine)的形式(AliSQL是阿里的MySQL分支)支持了许多业务,为用户带来了显著的成本和性能收益。

成都创新互联公司拥有一支富有激情的企业网站制作团队,在互联网网站建设行业深耕十余年,专业且经验丰富。十余年网站优化营销经验,我们已为超过千家中小企业提供了成都网站设计、做网站解决方案,按需定制开发,设计满意,售后服务无忧。所有客户皆提供一年免费网站维护!

时至今日,阿里巴巴数据库团队已经向MySQL官方提交了许多有价值的bug及修复方案。我们继承了这一优良传统,在生产、测试中遇到MySQL相关的问题,总是积极地思考解决方案,并迅速与官方交流沟通,为开源社区的发展贡献自己的力量。

下文将介绍我们刚发现的一个MySQL问题及修复方案。遇到相同情况的朋友需要注意了,也许不符合规范的数据已经写入你们的数据库了。

背景知识

如果MySQL参数sql_mode包含以下3项:

NO_ZERO_DATE

NO_ZERO_IN_DATE

STRICT_TRANS_TABLES

向DATE类型列,插入'0000-00-00'或者年/月/日3部分任意一部分为0,都将失败。

回馈开源,我如何排查一个MySQL Bug

异常:‘0000-00-00’竟然插入成功了

在MySQL 8.0.16上依次执行以下语句:

set sql_mode='';

create table test (mydate DATE NOT NULL DEFAULT '0000-00-00');
set sql_mode=default;

show variables like "sql_mode";

insert into test values();

select * from test;

这里先将sql_mode设为空的目的是:建表时将mydate的default value设为'0000-00-00',否则会因default value不符合NO_ZERO_DATE而建表失败。

建表成功后将sql_mode设回default,包含:

ONLY_FULL_GROUP_BY

NO_ZERO_DATE

NO_ZERO_IN_DATE

STRICT_TRANS_TABLES

ERROR_FOR_DIVISION_BY_ZERO

NO_ENGINE_SUBSTITUTION

然而,这时竟然成功向test库里插入了一条'0000-00-00'的DATE。显然,NO_ZERO_DATE的语义被打破了。

回馈开源,我如何排查一个MySQL Bug

抽丝剥茧,原来问题出在这

首先,我们定位到MySQL插入路径,检查default value是否合法的函数。

回馈开源,我如何排查一个MySQL Bug

这个函数比较简单,找出用户insert lists不包含的、且有default value的列,检查它们的default value是否合法。write_set是一个bitmap,标识了用户insert lists里包含哪些列。

我用gdb在该函数处加了断点,执行上述case,竟然发现write_set里全部bit被设为了1。这显然不正常的现象,我的插入SQL语句insert into test values();insert list明明为空,write_set全为0才合理。看来有函数错误地修改了它。

于是乎,我用gdb给write_set的地址加了一个watchpoint,重新执行insert语句。这次定位到了修改write_set的地方:

回馈开源,我如何排查一个MySQL Bug

该函数在检查default value是否合法前执行,其作用是当binlog_format为ROW且binlog_row_image为FULL时,write_set会被全部设置为1。

参数binlog_format指定了binlog格式,有三个备选项:

ROW代表主备之间通过log_event同步;

STATEMENT代表主备之间通过SQL语句同步;

MIXED则是混合格式,默认用STATEMENT方式,一些特殊情况下用ROW方式。

由于主备通过STATEMENT同步(虽然它产生的binlog数量小),可能因上下文信息、环境不同等因素,导致结果不一致,因此安全起见,binlog_format默认为ROW。

参数binlog_row_image指定了ROW格式binlog要记录哪些信息。它也有三个备选项:

FULL表示binlog记录变更前后的所有列;

MINIMAL表示binlog只记录唯一标识列和修改列;

NOBLOB表示BLOB是修改列或唯一标识列,才记录,其它列与FULL相同全部记录。

binlog_row_image默认为FULL。

当binlog_format为ROW且binlog_row_image为FULL 时,为了保证所有列都写到binlog里,write_set竟然被全部设置为1。

write_set变量本是用来标识用户插入列,又被赋予了控制写binlog的重任。多重语义交织,很容易出bug。这也给我们编码带来启示:每个变量应当有确切的语义。

修复建议

导致这个bug的原因是write_set用处太多。因此可以创建一个新的bitmap:binlog_write_set,专门用于控制写binlog。


文章名称:回馈开源,我如何排查一个MySQLBug
网站路径:http://cdxtjz.cn/article/psipoj.html

其他资讯