故障分析 | MySQL 主从切换后数据不一致了?

admin 2024-07-24 1068 阅读 0评论

1背景

近期生产上出现了个故障,某一套部署了 MySQL 主库的服务器由于硬件问题导致服务器宕机重启。由于该库部署了相关高可用组件,在服务器重启之后,该数据库服务被高可用组件拉起,并和新主库搭建了复制关系。

目前听起来没什么问题,蹊跷的是只复制了一会儿就报错了,复制中断报错信息大致是:Could not execute update_rows event on table xx; Can't find record in xx,Error_Code: 1032; handler error HA_ERR_KEY_NOT_FOUND;... 这到底是怎么回事呢?

2故障排查过程

(1)首先,第一反应是怀疑 主从切换是不是做的强制切换,导致新主上丢数据了?通过相关高可用组件的日志可以看到,切换时主从的 GTID 是一致的,即大致可认为该库在切换时未丢数据。无问题。

(2)再排查一些 slave 相关配置参数,看看是否是参数问题导致主从数据有不一致的潜在风险。无问题。

(3)接下来,在新从库执行 show slave status\G 时,发现新从库上自身比新主库多了一个 GTID。有问题。

(4)通过 mysqlbinlog -vvvv binlogs.xx --include-gtids=xx 解析这个多出的 GTID。发现该事务涉及的表和复制中断报错的表竟然是同一张表!

内容大致如下(本文中所有的表均是个人用于复现用的测试表,而非生产环境的表)。

BEGIN
/*!*/;
# at 349
#240716 12:33:45 server id 1002  end_log_pos 509 CRC32 0xbea1877e       Query   thread_id=9     exec_time=0     error_code=0
use `sky`/*!*/;
SET TIMESTAMP=1721104425/*!*/;
DELETE FROM `sky`.`sky_test` /* generated by server, implicitly emptying in-memory table */
/*!*/;
# at 509
#240716 12:33:45 server id 1002  end_log_pos 584 CRC32 0xd0443172       Query   thread_id=9     exec_time=0     error_code=0
SET TIMESTAMP=1721104425/*!*/;
COMMIT

分析上述内容可以发出以下疑问:

疑问一:明明已经设置 binlog 格式为 row,而且这张表实际上有多行数据,为什么 binlog 只记录了一条 SQL 语句?

疑问二:这个提示信息:/* generated by server, implicitly emptying in-memory table */ 好像和正常的不一样。看到 in-memory table,意识到好像不对劲,猜想这张表不会是内存表吧?

进一步查看该表的表结构。

mysql> show create table sky.sky_test\G
*************************** 1. row ***************************
       Table: sky_test
Create Table: CREATE TABLE `sky_test` (
  `id` int DEFAULT NULL
) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

果不其然,该表为内存(MEMORY[1])表。

因此,可以得出初步的结论:因为该表是内存表,新从库重启后数据库自身做了一个清空内存表的操作;而此时新主库对该表进行了更新操作,在新从库中无法找到相关的行记录,进而中断复制抛出错误。

3问题解决步骤

  1. 在新从库跳过该表的复制,并启动复制进程追平数据。
  2. 由于该表数据量不大,需要和业务确认该表的重要性并协调时间,在该表不发生任何改变时,在新主库导出数据并导入新从库中。然后在新从库取消跳过该表复制的操作。
  3. 后续要求应用将该表改造为 InnoDB 事务表。

实际上,为避免应用随意创建非事务表,可以在创建数据库实例时,通过一些参数设置,在数据库层面规避该问题,比如调整sql_mode和disabled_storage_engines参数。

当然如果非要用内存(MEMORY)表,想要避免这种类似的情况,官网[2] 给出的建议如下:

Note that the replica still has outdated data in a MEMORY table during the interval between the source's restart and its first use of the table. To avoid this interval when a direct query to the replica could return stale data, you can set the init_file system variable to name a file containing statements that populate the MEMORY table on the source at startup.

请注意,在源数据库重启和首次使用表之间的间隔期间,副本中的 MEMORY 表仍然包含过时的数据。为了避免在直接查询副本时可能返回陈旧数据的间隔,您可以设置 init_file 系统变量,以命名一个文件,该文件包含在启动时填充源数据库的 MEMORY 表的语句。

即便如此,还是不建议在 MySQL 中使用非事务表。

4答疑解惑

  1. 针对上面疑问一的解释。

    通过查看官方文档可以知道,表设置为 MEMORY 存储引擎后,在 binlog 中只会以 statement 的方式记录(即使已经设置 binlog 格式为 row )。

  2. delete from memory_table; 什么情况下才会触发呢,是一旦发生重启就会触发吗?

    经测试发现,应是数据库重启后首次访问该内存表时,才会触发清空操作。

5总结

要想保证数据库主从一致性,首先远离非事务表。

参考资料

[1]

memory-engine: https://dev.mysql.com/doc/refman/8.0/en/memory-storage-engine.html

[2]

replication-features-memory: https://dev.mysql.com/doc/refman/8.0/en/replication-features-memory.html


喜欢就支持以下吧
点赞 0

发表评论

快捷回复: 表情:
aoman baiyan bishi bizui cahan ciya dabing daku deyi doge fadai fanu fendou ganga guzhang haixiu hanxiao zuohengheng zhuakuang zhouma zhemo zhayanjian zaijian yun youhengheng yiwen yinxian xu xieyanxiao xiaoku xiaojiujie xia wunai wozuimei weixiao weiqu tuosai tu touxiao tiaopi shui se saorao qiudale qinqin qiaoda piezui penxue nanguo liulei liuhan lenghan leiben kun kuaikule ku koubi kelian keai jingya jingxi jingkong jie huaixiao haqian aini OK qiang quantou shengli woshou gouyin baoquan aixin bangbangtang xiaoyanger xigua hexie pijiu lanqiu juhua hecai haobang caidao baojin chi dan kulou shuai shouqiang yangtuo youling
提交
评论列表 (有 0 条评论, 1068人围观)

最近发表

热门文章

最新留言

热门推荐

标签列表