MySQL · 源码分析 · MySQL replication partial transaction

replication 概述

目前MySQL支持的replication方式多种多样

  1. 普通的master-slave 异步replication
  2. 半同步的semi-sync replication
  3. 支持多通道的group replication和double binlog

如果按连接协议来区分,又可以分为

  1. 非GTID模式,通过binlog文件名和文件的偏移来决定replication位点信息
  2. GTID模式,通过GTID信息来决定replication位点信息

如果按apply binglog的方式来区分,又可以分为

  1. 串行,按binlog event顺序依次执行
  2. 并行,以db, table或transaction为粒度的并行复制,以及基于group commit的LOGICAL_CLOCK并行复制

不论哪种replication, 都离不开replication最基本的组件,

  1. IO thread,负责从master拉取binlog.
  2. SQL thread,负责apply relay log binlog.

replication 异常

复制过程中,由于网络或者master主机宕机,都会造成slave IO thread异常中断。 例如以下事务在复制过程中发生上述异常,

SET GTID_NEXT; # GTID设置为ON时
BEGIN; INSERT row1; INSERT row2; COMMIT; 

那么备库接收的binlog可能不包含完整的事务,备库可能仅接收到BEGIN,也可能只接收到INSERT row1.

然而,当IO thread恢复后,SQL线程怎么正确处理这种异常呢?

异常恢复

IO thread 异常中断后,SQL线程是正常工作的,SQL执行了部分事务, 它会等待IO 线程发送新的binlog. IO thread 线程恢复后,SQL线程可以选择继续执行事务或者回滚事务重新执行事务,这是由replication协议决定的。

  1. GTID模式下,设置auto_position=1时,slave会根据GTID信息,从事务起点开始,重新将事务完整binlog发给备库。此时,备库需要回滚之前的部分事务。
  2. GTID模式下,设置auto_position=0或非GTID模式下,slave会根据位点信息从master续传之前的binlog。此时,备库可以继续完成之前的部分事务。

继续执行事务比较简单,但是回滚之前的部分事务就比较复杂.

分为两种情况来分析:

  • 串行复制

串行复制时,完整的事务会由SQL thread来执行,当执行到GTID_LOG_EVENT时,会发这个GTID已经分配过了,这时候就可以回滚事物。具体参考

Gtid_log_event::do_apply_event()

 if (thd->owned_gtid.sidno)
 {
 /*
 Slave will execute this code if a previous Gtid_log_event was applied
 but the GTID wasn't consumed yet (the transaction was not committed
 nor rolled back).
 On a client session we cannot do consecutive SET GTID_NEXT without
 a COMMIT or a ROLLBACK in the middle.
 Applying this event without rolling back the current transaction may
 lead to problems, as a "BEGIN" event following this GTID will
 implicitly commit the "partial transaction" and will consume the
 GTID. If this "partial transaction" was left in the relay log by the
 IO thread restarting in the middle of a transaction, you could have
 the partial transaction being logged with the GTID on the slave,
 causing data corruption on replication.
 */ if (thd->transaction.all.ha_list)
 {
 /* This is not an error (XA is safe), just an information */
 rli->report(INFORMATION_LEVEL, 0,
 "Rolling back unfinished transaction (no COMMIT " "or ROLLBACK in relay log). A probable cause is partial " "transaction left on relay log because of restarting IO " "thread with auto-positioning protocol.");
 const_cast<Relay_log_info*>(rli)->cleanup_context(thd, 1);
 }
 gtid_rollback(thd);
 }
  • 并行复制

并行复制有别于串行复制,binlog event由worker线程执行。按串行复制的方式来回滚事务是行不通的,因为重新发送的事务binlog并不一定会分配原来的worker来执行。因此,回滚操作需交给coordinate线程(即sql线程)来完成。 GTID模式下,设置auto_position=1时. IO thread重连时,都会发送 ROTATE_LOG_EVENT和FORMAT_DESCRIPTION_EVENT. 并且FORMAT_DESCRIPTION_EVENT的log_pos>0. 通过非auto_position方式重连的FORMAT_DESCRIPTION_EVENT的log_pos在send之前会被置为0. SQL线程通过执行FORMAT_DESCRIPTION_EVENT且其log_pos>0来判断是否应进入回滚逻辑。而回滚是通过构造Rollback event让work来执行的。
具体参考

exec_relay_log_event()
/*
 GTID protocol will put a FORMAT_DESCRIPTION_EVENT from the master with
 log_pos != 0 after each (re)connection if auto positioning is enabled.
 This means that the SQL thread might have already started to apply the
 current group but, as the IO thread had to reconnect, it left this
 group incomplete and will start it again from the beginning.
 So, before applying this FORMAT_DESCRIPTION_EVENT, we must let the
 worker roll back the current group and gracefully finish its work,
 before starting to apply the new (complete) copy of the group.
 */ if (ev->get_type_code() == FORMAT_DESCRIPTION_EVENT &&
 ev->server_id != ::server_id && ev->log_pos != 0 &&
 rli->is_parallel_exec() && rli->curr_group_seen_gtid)
 {
 if (coord_handle_partial_binlogged_transaction(rli, ev))
 /*
 In the case of an error, coord_handle_partial_binlogged_transaction
 will not try to get the rli->data_lock again.
 */
 DBUG_RETURN(1);
 }

MySQL官方针对此问题有过多次改进,详见以下commit

666aec4a9e976bef4ddd90246c4a31dd456cbca3
3f6ed37fa218ef6a39f28adc896ac0d2f0077ddb
9e2140fc8764feeddd70c58983a8b50f52a12f18

异常case处理

当slave SQL线程处于部分事务异常时,按上节的逻辑,IO thread恢复后,复制是可以正常进行的。但如果IO thread如果长时间不能恢复,那么SQL apply线程会一直等待新的binlog, 并且会一直持有事务中的锁。当slave切换为master后,新master会接受用户连接处理事务,这样SQL apply线程持有的事务锁,可能阻塞用户线程的事务。这是我们不希望看到的。

此时可以通过stop slave来停止SQL apply线程,让事务回滚释放锁。

另一种更好的方案是让SQL apply 线程自动识别这种情况,并加以处理。比如,增加等待超时机制,超时后自动kill sql 线程或回滚SQL线程的部分事务。

时间: 2024-10-14 22:55:29

MySQL · 源码分析 · MySQL replication partial transaction的相关文章

MySQL · 源码分析 · MySQL 半同步复制数据一致性分析

简介 MySQL Replication为MySQL用户提供了高可用性和可扩展性解决方案.本文介绍了MySQL Replication的主要发展历程,然后通过三个参数rpl_semi_sync_master_wait_point.sync_binlog.sync_relay_log的配置简要分析了MySQL半同步的数据一致性. MySQL Replication的发展 在2000年,MySQL 3.23.15版本引入了Replication.Replication作为一种准实时同步方式,得到广泛

MySQL · 源码分析 · MySQL BINLOG半同步复制数据安全性分析

半同步复制(semisynchronous replication)MySQL使用广泛的数据复制方案,相比于MySQL内置的异步复制它保证了数据的安 全,本文从主机在Server层提交事务开始一直到主机确认收到备机回复进行一步步解析,来看MySQL的半同步复制是怎么保证数 据安全的.本文基于MySQL 5.6源码,为了简化本文只分析DML的核心的事务处理过程,并假定事务只涉及innodb存储引擎. MySQL的事务提交流程 在MySQL中事务的提交Server层最后会调用函数ha_commit_

MySQL · 源码分析 · mysql认证阶段漫游

client发起一个连接请求, 到拿到server返回的ok包之间, 走三次握手, 交换了[不可告人]的验证信息, 这期间mysql如何完成校验工作? 过程(三次握手) 信息是如何加密的 client: hash_stage1 = sha1(password) hash_stage2 = sha1(hash_stage1) reply = sha1(scramble, hash_stage2) ^ hash_stage1 server: (逻辑位于sql/password.c:check_scr

MySQL · 源码分析 · Innodb 引擎Redo日志存储格式简介

MySQL有多种日志.不同种类.不同目的的日志会记录在不同的日志文件中,它们可以帮助你找出mysqld内部发生的事情.比如错误日志:用来记录启动.运行或停止mysqld进程时出现的问题:查询日志:记录建立的客户端连接和执行的语句:二进制日志:记录所有更改数据的语句,主要用于逻辑复制:慢日志:记录所有执行时间超过long_query_time秒的所有查询或不使用索引的查询.而对MySQL中最常用的事务引擎innodb,redo日志是保证事务一致性非常重要的.本文结合MySQL版本5.6为分析源码介

MySQL · 源码分析 · InnoDB 异步IO工作流程

之前的一篇内核月报InnoDB IO子系统 中介绍了InnoDB IO子系统中包含的同步IO以及异步IO.本篇文章将从源码层面剖析一下InnoDB IO子系统中,数据页的同步IO以及异步IO请求的具体实现过程. 在MySQL5.6中,InnoDB的异步IO主要是用来处理预读以及对数据文件的写请求的.而对于正常的页面数据读取则是通过同步IO进行的.到底二者在代码层面上的实现过程有什么样的区别? 接下来我们将以Linux native io的执行过程为主线,对IO请求的执行过程进行梳理. 重点数据结

MySQL · 源码分析 · SHUTDOWN过程

ORACLE 中的SHUTDOWN MySQL SHUTDOWN LEVEL 暂时只有一种,源码中留了 LEVEL 的坑还没填 在此借用 Oracle 的 SHUTDOWN LEVEL 分析 Oracle SHUTDOWN LEVEL 共有四种:ABORT.IMMEDIATE.NORMAL.TRANSACTIONAL ABORT 立即结束所有SQL 回滚未提交事务 断开所有用户连接 下次启动实例时,需要recovery IMMEDIATE 允许正在运行的SQL执行完毕 回滚未提交事务 断开所有用

MySQL · 源码分析 · InnoDB LRU List刷脏改进之路

之前的一篇内核月报MySQL · 引擎特性 · InnoDB Buffer Pool 中对InnoDB Buffer pool的整体进行了详细的介绍.文章已经提到了LRU List以及刷脏的工作原理.本篇文章着重从MySQL 5.7源码层面对LRU List刷脏的工作原理,以及Percona针对MySQL LRU Flush的一些性能问题所做的改进,进行一下分析. 在MySQL中,如果当前数据库需要操作的数据集比Buffer pool中的空闲页面大的话,当前Buffer pool中的数据页就必须

MySQL · 源码分析 · 一条insert语句的执行过程

本文只分析了insert语句执行的主路径,和路径上部分关键函数,很多细节没有深入,留给读者继续分析 create table t1(id int); insert into t1 values(1) 略过建立连接,从 mysql_parse() 开始分析 void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state) { /* ...... */ /* 检查query_cache,如果结果存在于c

MySQL · 源码分析 · 词法分析及其性能优化

Table of Contents 1. 简介 2. 背景知识 3. 查找树的实现 3.1. 树的查找 3.2. 树的产生 4. 试试折半查找 5. 总结 简介 MySQL 支持标准的 SQL 语言,具体实现的时候必然要涉及到词法分析和语法分析.早期的程序可能会优先考虑手工实现词法分析和语法分析,现在大多数场合下都会采用工具来简化实现.MySQL.PostgreSQL 等采用 C/C++ 实现的开源数据库采用的是现代的 yacc/lex 组合,也就是 GNU bison/flex.其他比较流行的