本篇文章介绍了一类因为存储数据“0”导致的数据库集群故障。
回顾上篇文章:Mesos 实战之 Constraints
(封面图片源自:pixabay jplenio)
背景
线上数据一致性排查的时候,发现某数据库集群存在少量数据不一致的现象,可是该集群近期并没有进行过主库切换,也没有出现其他可能导致该集群数据不一致的故障。
经过仔细排查对比发现,该集群某表中一个数据类型为 double 的列,主库中存储的数据为 "-0",而从库同步的数据却为 "0",如此导致了在使用数据一致性工具检查的时候发现了数据不一致的 chunk。
提出问题
先在测试环境创建一张简单的表来进行数据测试。
问题 1:奇怪的 - 0
手动插入数据 - 0 问题复现失败,由于线上服务使用的 Prepared Statement 来进行数据操作,常规 Query 无法复现 - 0 的情况,于是采用 PS 的测试用例,成功复现 - 0,测试代码如下。
复现结果:
可以看到普通 Query 插入的值被转换为 0,用 prepared statement 形式插入的值为 - 0,而 - 0 并没有被同步到从库中,复现线上问题。
问题 2:为什么同步不一致
由于上面的实验过程,同步不一致的原因已经比较明确了。由于线上服务主从同步 binlog_format 设置的为 MIXED 格式,生成的 binlog 语句不再是 prepared statement 形式,在从库重放时 - 0 被转换成 0,导致数据不一致。
通过实验可以看到,修改 binlog_format 后同步一致。
源码分析
(Version MySQL-Server 5.5)
上面这两个问题,本质上都是由一个问题导致的,即 MySQL 中 Prepared Statement 的处理与普通 Query 的处理逻辑不同。我们可以开启 MySQL 的 trace log,结合源码了解这其中的原因。
查看 MySQL 源码结合 trace log,MySQL 中处理 Query 的入口为文件中的函数,该函数会获取 Query 语句,
并在该函数中对不同形式的 Query Statement 进行分发处理。
以下为部分 MySQL 源码:
继续追查,发现其最终使用的数据落盘的函数为,可以看到是没有做转换直接写到数据文件中的。
而 Query Statement 在解析过程中 double 类型的数据存在精度丢失,从而导致插入 - 0 时会得到 0。
结论
最终该问题的原因为以 Prepared Statement 形式的 SQL 在执行的时候数据直接落盘,未进行数据转换,-0 可以被写入到数据库中。
而在 binary log 的格式为MIX的时候,这一条 SQL 的 binary log 会被转换为为STATEMENT格式, 从库同步的时候以非 Prepare 的形式插入,精度丢失,导致从库插入的数据为 0,造成数据不一致。
而 ROW 格式的 binary log 记录了数据的变化,所以会把 - 0 完好的同步到从库当中。
参考资料
golang negative zero
MySQL prepared statemets
MySQL binary log setting
MySQL source code
领取专属 10元无门槛券
私享最新 技术干货