前言
在MySQL的binlog中,一个事务一般由多个binlog events组成。在复制(或者崩溃恢复)等场景中,为了保证数据的一致性,需要根据当前已经存在binlog中的event来判断事务的完整性,以决定复制的位置(或者是否需要truncate不完整的事务)。MySQL 5.7开始,引入了类Transaction_boundary_parser来对一个完整事务做边界的判断。事务边界判断的源代码包含在sql/rpl_trx_boundary_parser.h(cc)两个文件中,以下内容是相关源码的分析。
事件的边界类型
在rpl_trx_boundary_parser.h中,MySQL的binlog event被划分为以下几类:
- EVENT_BOUNDARY_TYPE_GTID,主要是Gtid_log_event;
- EVENT_BOUNDARY_TYPE_BEGIN_TRX, 包括Query_log_event(BEGIN)和Query_log_event(XA START);
- EVENT_BOUNDARY_TYPE_END_TRX,包括Xid, Query_log_event(COMMIT), Query_log_event(ROLLBACK), XA_Prepare_log_event等事件;
- EVENT_BOUNDARY_TYPE_END_XA_TRX,主要是Query_log_event(XA ROLLBACK);
- EVENT_BOUNDARY_TYPE_PRE_STATEMENT,包括User_var, Intvar, Rand上下文相关的event;
- EVENT_BOUNDARY_TYPE_STATEMENT, 包括其他所有的Query_log_events和DML事件(Rows, Load_data等);
- EVENT_BOUNDARY_TYPE_IGNORE, 包括所有非DDL/DML事件,例如:Format_desc, Rotate, Incident, Previous_gtids, Stop等;
- EVENT_BOUNDARY_TYPE_ERROR, 非以上类型的event归结为此类;
事务序列的定义
DDL事务序列
1 | DDL-1: [GTID] |
DML事务序列
1 | DML-1: [GTID] |
说明
- DDL和DML事务由不同的binlog事件序列组成,在开启GTID的时候,都是以GTID事件开始;
- DML事务的开始和结束有相对明显的事件标识,通常以BEGIN或者XA START事件作为事务的开始,以COMMIT/ROLLBACK等事务的提交作为结束;
- DDL事务的结束相对难以确定,其中DDL-2中的三种binlog事件是一种预处理事件,User代表语句中使用了用户变量,Intvar代表语句中使用了INSERT_ID或LAST_INSERT_ID,Rand代表语句中使用了Rand()函数, 在ROW格式的binlog中,不存在预处理事件;
事务边界判断的状态机
根据以上事务序列的定义,可以将事务边界的判断抽象为一个状态机,该状态机包含一下几种状态:
- EVENT_PARSER_NONE,开始状态,在遇到DDL-3或者DML-4类型的event后,跳转到该状态,表示上一个事务的结束,一个新事物的开始;
- EVENT_PARSER_GTID, GTID状态,在遇到DDL-1或者DML-1类型的event(其实就是Gtid_log_event)后,跳转到该状态,不开启GTID时,不存在该状态;
- EVENT_PARSER_DDL, DDL状态,在遇到DDL-2类型的event后,跳转到该状态,表示当前在一个DDL事务中;
- EVENT_PARSER_DML, DML状态,在遇到DML-2类型的event(BEGIN)后,跳转到该状态,表示当前在一个DML事务中;
- EVENT_PARSER_ERROR, 错误状态,遇到非正常事件序列后,跳转到该状态;
状态机的跳转逻辑图如下:
注意:
不管前驱状态如何,只要遇到错误的边界类型,状态机都会转移成ERROR,为了使逻辑图清晰,这部分没有画!
参考
- 本文内容来自Mysql 5.7源代码sql/rpl_trx_boundary_parser.h(cc)两个文件,感兴趣可研究一下。