回馈开源,我如何排查一个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,都将失败。
异常:‘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插入路径,检查default value是否合法的函数。
这个函数比较简单,找出用户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的地方:
该函数在检查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://myzitong.com/article/psipoj.html