MySQL事务还没提交,Canal就能读到消息了?

【问题描述】

开发有天碰到一个很奇怪的问题,他的场景是这样子的:
通过Canal来订阅MySQL的binlog, 当捕获到有数据变化时,回到数据库,反查该数据的明细,然后做进一步处理。
有一次,他碰到一个诡异的现象:

1.  Canal收到消息,有一条主键id=31019319的数据插入
2.  11:19:51.081, 应用程序去反查数据库,11:19:51.084查询完毕,发现id=31019319的数据为空
3.  过几分钟后,开发去手工查数据库,发现id=31010319的数据是存在的,每次插入的时候,我们会在数据库记录插入时间,发现插入的时间是11:19:51.059。

让开发感到困惑的是11:19:51.059写入的数据,11:19:51.081去查询,应该是能查到数据的呀。我们首先排除了读写分离,主从分离等场景,Canal订阅和数据库查询都是在Master上,所以这个问题就变得非常诡异了。

【问题分析】

因为中间夹杂着Canal, 而Canal是通过binlog读取的,这个问题我们可以简化为:当我们在master插入一条数据,该数据在master还没落库,但是在Slave却能查到。我们尝试重现这种场景。因为我们是采用GTID模式,GTID也就是全局事务编号,我们通过跟踪GTID来调试问题。

我们创建一个测试表如下:

CREATE TABLE `gtid_debug` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

此时,在Master和Slave上,分别收集到的GTID信息如下:

角色 @@global_gtid_executed @@port
Master be7945f1-3613-11ec-8353-98039ba5775a:1-16 3306
Slave be7945f1-3613-11ec-8353-98039ba5775a:1-16 3307

我们在Master上开启gdb调试,在函数ReplSemiSyncMaster::commitTrx上设置断点。

步骤1:

在Master上,开启Session1, 插入一条数据:

insert into gtid_debug(name)values('test1'); 

此时会hit到断点。

步骤2:

在Slave上,开启Session2, 查看GTID:

角色 @@global_gtid_executed @@port
Slave be7945f1-3613-11ec-8353-98039ba5775a:1-17 3307

也就是说,事务在Slave上,开始走字了。
我们进行如下查询:可以看到,在Slave这条记录能被查询到。

slave>select * from test.gtid_debug;
| ID   | NAME  |
| ---- | ----- |
| 1    | test1 |

步骤3:

在Master上,我们开启Session3, 查看GTID, 这个session也会被断点中断,我们继续执行下一步,直到查询结果返回。注意,此时Session1还停留在断点上,未提交成功。

角色 @@global_gtid_executed @@port
Master be7945f1-3613-11ec-8353-98039ba5775a:1-16 3306

并进行如下查询,返回结果为空:

master>select * from test.gtid_debug;
Empty set 

所以我们重现了问题,也就是说,在Master插入数据,事务还没有提交,但在Slave就能查到了。 Slave跑的比Master还快。

【原因分析】

重现了问题后,我们对问题进行分析,并查看了相应代码,发现是半同步复制的模式导致,半同步复制有两种模式: After_Sync(5.7版本默认)模式和After_Commit(5.6版本默认)模式。我们线上的版本是5.7,所以采用的是After_Sync模式。
MySQL事务还没提交,Canal就能读到消息了?

从上图可以看到,一个事务在半同步模式下提交,无论是after_sync还是after_commit,都要经历4个阶段:

1. InnoDB Redo File Prepare Write
2. Binlog File Flush & Sync
3. InnoDB Redo File Commit (同时释放事务持有的锁)
4. Send binlog to Slave

After_Commit模式的四个阶段顺序为: 1->2->3->4, 而after_sync模式的顺序为1->2->4->3.

在5.7默认的after_sync模式下,确实存在先发送binlog到Slave, 然后再进行事务提交的场景。这时候大家会问了,为啥5.7把半同步复制改为after_sync模式了?这主要是因为after_commit机制存在数据丢失的风险。我们可以设想一下,在3->4的T1时间段,新数据对其它Session已经可见,突然Master挂了,MySQL进行主从切换,这时事务在Master上完成,如在Slave上不存在,切换后,业务会发现之前能查到的数据又没了。

而在after_sync模式下,其执行的顺序为1->2->4->3. 也就是说Master在收到Slave的应答之后,才Commit事务。在3->4的T1时间段内,因事务还未Commit,新数据对其它Session还不可见,所以看上去像比Slave跑的更慢。具体可以参考网上关于这两种模式的讨论。

【解决建议】

我们分析清楚问题之后,解决的方法就比较简单了。不建议改为after_commit模式,虽然改为after_commit模式,可以保证事务在Master落地后,Canal才会读到消息,但存在主从切换事务丢失的风险。我们的解决方法,是在Canal消息处理时,延后1秒再处理。这样解决方法比较合理。因为一般来讲,业务对消息的实时性不是特别高。

原文链接:https://www.cnblogs.com/CtripDBA/p/17265902.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:MySQL事务还没提交,Canal就能读到消息了? - Python技术站

(0)
上一篇 2023年4月18日
下一篇 2023年4月18日

相关文章

  • MySQL冷备份所需物理文件

    MySQL冷备份是一种备份方式,它的特点是备份过程中数据库不会被访问或修改。这种备份方式可以在数据库运行期间进行,不会对正常业务产生影响,并且备份文件的大小、恢复速度、稳定性都比较好。 在进行MySQL冷备份时,需要备份一些物理文件。 数据库文件 MySQL的数据库文件通常存储在数据目录下,这些文件包括数据文件(.frm、.ibd等)和日志文件(.ib_lo…

    MySQL 2023年3月10日
    00
  • 读SQL进阶教程笔记03_自连接

    1. 针对相同的表进行的连接 1.1. 相同的表的自连接和不同表间的普通连接并没有什么区别,自连接里的“自”这个词也没有太大的意义 1.2. 与多表之间进行的普通连接相比,自连接的性能开销更大 1.2.1. 特别是与非等值连接结合使用的时候 1.2.2. 用于自连接的列推荐使用主键或者在相关列上建立索引 2. 组合 2.1. 有顺序的有序对(ordered …

    MySQL 2023年4月18日
    00
  • MySQL日志文件详解

    MySQL日志文件详解 什么是MySQL日志文件 MySQL日志文件是指MySQL服务器记录在磁盘上的各种操作信息,这些信息主要用于监管MySQL的运行情况,便于排查问题和开发调试等。MySQL日志文件主要分为以下几种: General Log(常规日志):记录MySQL服务器执行的所有的SQL语句以及其他重要的事件。 Error Log(错误日志):记录M…

    MySQL 2023年5月18日
    00
  • MySQL 5.6下table_open_cache参数优化合理配置详解

    MySQL的table_open_cache参数是控制MySQL数据库中打开表的缓存数量的参数。合理配置table_open_cache参数能够有效的提升MySQL数据库的性能。下面就是一个关于MySQL 5.6下table_open_cache参数优化合理配置的详细攻略。 什么是table_open_cache参数 table_open_cache参数是M…

    MySQL 2023年5月19日
    00
  • MySQL limit性能分析与优化

    MySQL的limit是一种非常常用的限制查询结果的方法,但是当limit条件设置较大时,可能会导致查询效率比较低下。因此针对limit可能存在性能问题,需要进行性能分析与优化的工作。 以下是“MySQL limit性能分析与优化”的完整攻略: 1.性能分析 1.1 查询分析 优化limit查询的第一步是明确查询语句的具体执行情况。可以使用EXPLAIN命令…

    MySQL 2023年5月19日
    00
  • MySql 安装时的1045错误

    MySQL 安装时的 1045 错误通常是因为用户名或密码输入错误或者没有授权的账户尝试连接MySQL数据库,导致连接被拒绝。如果你遇到了这个问题,可以按照以下步骤解决。 错误示例 当导入数据库时,出现以下错误: ERROR 1045 (28000): Access denied for user ‘root’@’localhost’ (using pass…

    MySQL 2023年5月18日
    00
  • MySQL不停地自动重启的解决方法

    当MySQL出现问题导致自动重启时,可以通过以下几个步骤解决: 检查MySQL日志 首先需要检查MySQL的错误日志(error log),查看MySQL重启的原因。可以打开MySQL配置文件(一般在/etc/mysql/my.cnf),找到以下行: log_error = /var/log/mysql/error.log 然后查看error.log文件,查…

    MySQL 2023年5月18日
    00
  • 解决MySQL 5.7.9版本sql_mode=only_full_group_by问题

    当MySQL的版本为5.7.9及以上时,启动sql_mode为only_full_group_by时,可能会导致部分SQL语句执行异常。本攻略将会介绍如何解决这个问题。 问题描述 在MySQL 5.7.9及以上版本中,启动sql_mode为only_full_group_by时,如果有GROUP BY的SQL语句中包含非GROUP BY中的字段,MySQL会…

    MySQL 2023年5月18日
    00
合作推广
合作推广
分享本页
返回顶部