MySQL 多事务引擎XA

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介:
有那么一坨代码,他虽然在那里,我们却很少用到。。那就是MySQL的多XA事务引擎特性支持。。本周我们来探讨下TC LOG MMAP的代码实现。由于工作的关系,这块很少涉及,正好趁着周末补补漏。

 

本文分析的代码基于支持Tokudb的MySQL5.6.16 和MySQL 5.7.5;原因是官方MySQL还不包含多个事务引擎,因此代码压根走不到TC LOG MMAP. 但是MySQL5.7又Fix掉了相关的XA BUG (bug#47134),因此本文贴出的代码部分以MySQL5.7.5为主,调试部分以我们支持TOKUDB的MySQL5.6.16内部分支为主。

1.准备工作
为了能够使用到TC LOG MMAP,我们需要禁止binlog和gtid。 如果仅仅关掉binlog,但gtid没有关闭,也会阻止实例启动

 

2.实例启动时的初始化
在实例启动时,会选择使用哪种XA方式,默认的就是BINLOG和ENGINE做XA,如果BINLOG禁止了,则使用引擎本身做XA
相关代码(sql/mysqld.cc:  init_server_components):
4011   if (total_ha_2pc > 1 || (1 == total_ha_2pc && opt_bin_log))
4012   {
4013     if (opt_bin_log)
4014       tc_log= &mysql_bin_log;
4015     else
4016       tc_log= &tc_log_mmap;
4017   }
4018   else
4019     tc_log= &tc_log_dummy
其中total_ha_2pc代表了支持了二阶段提交(2PC)的引擎数目,在初始化各个存储接口时(函数ha_initialize_handlerton),如果发现引擎的prepare接口函数被定义了,就会给total_ha_2pc++; 注意binlog模块也算是支持2PC的引擎
#不存在支持2PC的引擎,或者只有1个支持2PC的引擎且没有打开Binlog时,使用tc_log_dummy做协调者;对应类TC_LOG_DUMMY
#打开binlog时,使用mysql_bin_log做2PC协调者; 对应类MYSQL_BIN_LOG
#没有打开binlog,且存在超过两个支持2PC的引擎时,使用tc_log_mmap做协调者;对应类TC_LOG_MMAP

 

这三个类对象皆继承自TC_LOG,关系图如下:

 

  tc_log

 

对于TC_LOG_DUMMY,直接调用引擎接口做PREPARE/COMMIT/ROLLBACK,基本不做任何协调;
对于MYSQL_BIN_LOG,这算是我们最熟悉的部分了,实际上binlog接口不做prepare,只做commit,也就是写BINLOG到文件中。所有写入Binlog的事务,在崩溃恢复时,都应该能够提交。

 

本文的重点是TC_LOG_MMAP,因此其他两类不在这里展开阐述。

 

注意下文所有的讨论前提,都是基于BINLOG已经被彻底关闭!!!

 

3. TC LOG初始化
在确定了使用tc_log_mmap后,就会调用TC_LOG_MMAP::open 打开/初始化日志文件,文件名默认为tc.log,存放在$DATA目录下,文件主要用来持久化维护事务的XID信息

 

当tc.log文件不存在时,就创建该文件,并将文件大小初始化到24KB
当tc.log文件存在时,表示上次使用的是tc_log_mmap,那么就需要走崩溃恢复逻辑。

 

对tc.log的操作,使用mmap的方式映射到内存中:

  data= (uchar *)my_mmap(0, (size_t)file_length, PROT_READ|PROT_WRITE,
                        MAP_NOSYNC|MAP_SHARED, fd, 0);

 

tc.log 以PAGE来进行划分,每个PAGE大小为8K,至少需要3个PAGE,初始化的文件大小也为3个PAGE(TC_LOG_MIN_SIZE),每个Page对应的结构体对象为st_page,因此需要根据page数,完成文件对应的内存控制对象的初始化。

 

在完成st_page初始化后,如果需要进入崩溃恢复逻辑(启动实例时tc.log文件已经存在),则调用TC_LOG_MMAP::recover进入崩溃恢复处理 (后文讨论)

 

初始化第一个page的header,写入magic number以及当前的2PC引擎数(也就是total_ha_2pc)

 

下图描述了tc.log的文件结构:

 

mmap

 

4.事务Prepare/Commit
假定我们做了如下操作序列,其中sbtest1为Innodb表,sbtest2为tokudb表;

 

BEGIN;
update sbtest1 set k =k+1 where id = 2;
update sbtest2 set k =k+1 where id = 2;
commit;

 

当遇到第一个UPDATE时,会去注册XA,设置XID。backtrace如下:
mysql_update
|–>lock_table –>mysql_lock_tables–>lock_external–>handler::ha_external_lock–>ha_innobase::external_lock–>innobase_register_trx–>trans_register_ha

 

if (thd->transaction.xid_state.xid.is_null())
thd->transaction.xid_state.xid.set(thd->query_id);

 

XID根据会话当前的query_id来设置,由于query id是递增的,因此能保证xid唯一性。
(如果是先UPDATE TOKUDB表,则是另外一个trace:lock_external–>handler::ha_external_lock–>ha_tokudb::external_lock–>ha_tokudb::create_txn-->trans_register_ha)

 

总之,不管先更新哪个表,xid只注册一次,在事务完成前都不会变化了。

 

当事务提交COMMIT时,实际上是分两步走的,第一步是Prepare,第二步是Commit;

 

mysql_execute_command–>trans_commit–>ha_commit_trans:
    if (!trn_ctx->no_2pc(trx_scope) && (trn_ctx->rw_ha_count(trx_scope) > 1))
      error= tc_log->prepare(thd, all);

 

当事务中使用超过两个XA事务引擎时,就会走到XA PREPARE的逻辑,如果只使用一个引擎,是无需PREPARE的。

 

TC_LOG_MMAP::prepare的实现很简单,就是直接调用引擎的PREPARE接口函数(ha_prepare_low)

 

通常事务引擎都会在引擎层写一个PREPARE的REDO 日志,例如TOKUDB和INNODB。

 

当完成Prepare后,就进入Commit阶段,这里会稍微复杂点。XA事务COMMIT的入口为TC_LOG_MMAP::commit, 分为以下几个步骤:

 

Step 1: 获取XID
my_xid xid= thd->transaction.xid_state.xid.get_my_xid();
在遇到第一个UPDATE时XID已经设置好了。对应unsigned longlong的query id

 

Step2: 记录XID到tc.log中
if (all && xid)
      if (!(cookie= log_xid(thd, xid)))
          DBUG_RETURN(RESULT_ABORTED); // Failed to log the transaction
为了将XID写入到TC日志中,首先需要选择一个PAGE。
TC_LOG_MMAP对PAGE的管理,采用一种POOL结构,在刚启动时的初始化时,active指针指向第一个Page, pool指针指向第二个page, pool_last_ptr指向最后一个page结尾.
通过active、pool、pool_last_ptr三个指针实现了PAGE POOL的管理。
记录XID的流程如下:
1)持有LOCK_tc锁
2)获取active page
–#如果当前active的page中没有空闲空间,则condition wait (COND_active)
–#如果没有active的page,则从POOL中取一个PAGE(TC_LOG_MMAP::get_active_from_pool),根据注释描述,有两种策略:
–# take the first from the pool
–# if there’re waiters – take the one with the most free space.
然后将active指针指向选择的page
3)将xid写入到active page中 (store_xid_in_empty_slot),Page状态被设置成PS_DIRTY。
同时返回一个cookie:
cookie= (ulong)((uchar *)p->ptr – data_arg). 通过cookie,我们可以快速定位到写入的page位置
4)如果syncing指针被设置,表示有别的线程正在sync文件,等待直到其完成(wait_sync_completion)
注意这里可能别的线程sync的正是当前page,因此再次检查st_page::state是否为PS_DIRTY。如果不是,就无需去sync 当前page了,直接返回;
5)将active的page指针赋值给syncing,同时设置active为NULL.
6)释放LOCK_tc锁。
7)写syncing的page,刷到磁盘,调用函数TC_LOG_MMAP::sync
完成sync后,持有LOCK_tc,将该page丢到POOL末尾,唤醒可能在step4等待的线程。syncing被重置为NULL.
可以看到,在写一个XID的过程中,PAGE经历了,从ACTIVE ==> SYNCING ==> POOL的三种状态转变。

 

Step3: 到引擎层提交事务(ha_commit_low)
不赘述,各个引擎各自实现。

 

Step4: 重置XID.
if (cookie)
        if (unlog(cookie, xid))
                DBUG_RETURN(RESULT_INCONSISTENT); // Transaction logged, committed, but not unlogged.
调用函数TC_LOG_MMAP::unlog,前面提到的cookie这里就起到作用了,我们可以直接定位到PAGE的内存位置:
PAGE *p= pages + (cookie / tc_log_page_size);
my_xid *x= (my_xid *)(data + cookie);
将在记录的XID设置为0,同时递增st_page::free,表示已经释放了一个空闲的slot。

 

5.崩溃恢复
崩溃恢复时,我们需要所有已经记录的XID都要COMMIT掉。
函数TC_LOG_MMAP::recover
和BINLOG做XA Recover类似,TC_LOG_MMAP也是在崩溃恢复时,读取记录在tc.log文件中的XID;所有已经记录的XID对应的事务,在引擎层都需要COMMIT

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
MySQL底层概述—9.ACID与事务
本文介绍了数据库事务的ACID特性(原子性、一致性、隔离性、持久性),以及事务控制的演进过程,包括排队、排它锁、读写锁和MVCC(多版本并发控制)。文章详细解释了每个特性的含义及其在MySQL中的实现方式,并探讨了事务隔离级别的类型及其实现机制。重点内容包括:ACID特性(原子性、持久性、隔离性和一致性的定义及其实现方式)、事务控制演进(从简单的全局排队到复杂的MVCC,逐步提升并发性能)、MVCC机制(通过undo log多版本链和Read View实现高效并发控制)、事务隔离级别(析了四种隔离级别(读未提交、读已提交、可重复读、可串行化)的特点及适用场景)、隔离级别与锁的关系。
MySQL事务日志-Undo Log工作原理分析
事务的持久性是交由Redo Log来保证,原子性则是交由Undo Log来保证。如果事务中的SQL执行到一半出现错误,需要把前面已经执行过的SQL撤销以达到原子性的目的,这个过程也叫做"回滚",所以Undo Log也叫回滚日志。
163 7
MySQL事务日志-Undo Log工作原理分析
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
事务是MySQL中一组不可分割的操作集合,确保所有操作要么全部成功,要么全部失败。本文利用SQL演示并总结了事务操作、事务四大特性、并发事务问题、事务隔离级别。
1869 2
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
MySQL进阶突击系列(04)事务隔离级别、AICD、CAP、BASE原则一直搞不懂? | 看这篇就够了
本文详细介绍了数据库事务的四大特性(AICD原则),包括原子性、隔离性、一致性和持久性,并深入探讨了事务并发问题与隔离级别。同时,文章还讲解了分布式系统中的CAP理论及其不可能三角关系,以及BASE原则在分布式系统设计中的应用。通过具体案例和图解,帮助读者理解事务处理的核心概念和最佳实践,为应对相关技术面试提供了全面的知识准备。
MySQL引擎InnoDB和MyISAM的区别?
InnoDB是MySQL默认的事务型存储引擎,支持事务、行级锁、MVCC、在线热备份等特性,主索引为聚簇索引,适用于高并发、高可靠性的场景。MyISAM设计简单,支持压缩表、空间索引,但不支持事务和行级锁,适合读多写少、不要求事务的场景。
100 9
MySQL的事务隔离级别
【10月更文挑战第17天】MySQL的事务隔离级别
183 43
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1997 14
MySQL事务日志-Redo Log工作原理分析
mysql事务特性
原子性:一个事务内的操作统一成功或失败 一致性:事务前后的数据总量不变 隔离性:事务与事务之间相互不影响 持久性:事务一旦提交发生的改变不可逆
MySQL事务隔离级别及默认隔离级别的设置
在数据库系统中,事务隔离级别是一个关键的概念,它决定了事务在并发执行时如何相互隔离。MySQL提供了四种事务隔离级别,每种级别都解决了不同的并发问题。本文将详细介绍这些隔离级别以及MySQL的默认隔离级别。
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
从基本特性、技术选型、字段类型、事务提交方式、SQL语句、分页方法等方面对比Oracle和MySQL的区别。
1321 18
下一篇
oss创建bucket