MySQL 多事务引擎XA

有那么一坨代码,他虽然在那里,我们却很少用到。。那就是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_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的文件结构:

 

 

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

时间: 2024-08-03 23:13:28

MySQL 多事务引擎XA的相关文章

MySQL分布式事务(XA事务)

MySQL分布式事务(XA事务) 官网:https://dev.mysql.com/doc/refman/5.7/en/xa.html 1.什么是分布式事务 分布式事务就是指事务的参与者.支持事务的服务器.资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上.以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败.本质上来说,分布式事务就是为了保证不同数据库的数据一致

MySQL · 特性分析 · MySQL 5.7 外部XA Replication实现及缺陷分析

MySQL 5.7 外部XA Replication实现及缺陷分析 MySQL 5.7增强了分布式事务的支持,解决了之前客户端退出或者服务器关闭后prepared的事务回滚和服务器宕机后binlog丢失的情况. 为了解决之前的问题,MySQL5.7将外部XA在binlog中的记录分成了两部分,使用两个GTID来记录.执行prepare的时候就记录一次binlog,执行commit/rollback再记录一次.由于XA是分成两部分记录,那么XA事务在binlog中就可能是交叉出现的.Slave端的

MySQL数据库存储引擎和分支现状

在MySQL经历了2008年Sun的收购和2009年Oracle收购Sun的过程中,基本处于停滞发展的情况,在可以预见的未来,MySQL是肯定会被Oracle搁置并且逐步雪藏消灭掉的.MySQL随着相应的各主创和内部开发人员的离去,缔造了各个不同的引擎和分支,让MySQL有希望继续发扬光大起来. 本文大致讲解一下MySQL目前除了主要的 MyISAM.InnoDB.Heap(Memory).NDB 等引擎之外的其他引擎的发展和现状,以及MySQL主干以外的分支的状况,为了我们未来更好的使用MyS

mysql 的存储引擎介绍

在数据库中存的就是一张张有着千丝万缕关系的表,所以表设计的好坏,将直接影响着整个数据库.而在设计表的时候,我们都会关注一个问题,使用什么存储引擎.等一下,存储引擎?什么是存储引擎? 什么是存储引擎? MySQL中的数据用各种不同的技术存储在文件(或者内存)中.这些技术中的每一种技术都使用不同的存储机制.索引技巧.锁定水平并且最终提供广泛的不同的功能和能力.通过选择不同的技术,你能够获得额外的速度或者功能,从而改善你的应用的整体功能. 例如,如果你在研究大量的临时数据,你也许需要使用内存MySQL

mysql存储过程事务管理简析_Mysql

ACID:Atomic.Consistent.Isolated.Durable 存储程序提供了一个绝佳的机制来定义.封装和管理事务. 1,MySQL的事务支持 1)MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关: Sql代码 复制代码 代码如下: MyISAM:不支持事务,用于只读程序提高性能 InnoDB:支持ACID事务.行级锁.并发 Berkeley DB:支持事务 MyISAM:不支持事务,用于只读程序提高性能 InnoDB:支持ACID事务.行级锁.并发 Ber

MySQL数据库存储引擎和分支现状分析_Mysql

MySQL随着相应的各主创和内部开发人员的离去,缔造了各个不同的引擎和分支,让MySQL有希望继续发扬光大起来.  在MySQL经历了2008年Sun的收购和2009年Oracle收购Sun的过程中,基本处于停滞发展的情况,在可以预见的未来,MySQL是肯定会被Oracle搁置并且逐步雪藏消灭掉的.MySQL随着相应的各主创和内部开发人员的离去,缔造了各个不同的引擎和分支,让MySQL有希望继续发扬光大起来. 本文大致讲解一下MySQL目前除了主要的 MyISAM.InnoDB.Heap(Mem

mysql更改表引擎INNODB为MyISAM的方法总结

常见的mysql表引擎有INNODB和MyISAM,主要的区别是INNODB适合频繁写数据库操作,MyISAM适合读取数据库的情况多一点,如何把表引擎INNODB更改为MyISAM呢? 使用以下mysql sql语句,可以给表设定数据库引擎: ALTER TABLE `wp_posts` ENGINE = MyISAM; 在需要使用mysql的全文索引(FULLTEXT index)的时候,这张表的数据库引擎必须是MyISAM类型.关于INNODB为MyISAM数据库引擎有什么具体区别 例子 修

详解MySQL下InnoDB引擎中的Memcached插件_Mysql

前些年,HandlerSocket的横空出世让人们眼前一亮,当时我还写了一篇文章介绍了其用法梗概,时至今日,由于种种原因,HandlerSocket并没有真正流行起来,不过庆幸的是MySQL官方受其启发,研发了基于InnoDB的Memcached插件,总算是在MySQL中延续了NoSQL的香火,以前单独架设Memcached服务器不仅浪费了内存,而且还必须自己维护数据的不一致问题,有了Memcached插件,这些问题都不存在了,而且借助MySQL本身的复制功能,我们可以说是变相的实现了Memca

修改MySQL的数据库引擎为INNODB的方法_Mysql

对于MySQL数据库,如果你要使用事务以及行级锁就必须使用INNODB引擎.如果你要使用全文索引,那必须使用myisam. INNODB的实用性,安全性,稳定性更高但是效率比MYISAM稍差,但是有的功能是MYISAM没有的.修改MySQL的引擎为INNODB,可以使用外键,事务等功能,性能高.本文主要介绍如何修改MySQL数据库引擎为INNODB,接下来我们开始介绍. 首先修改my.ini,在[mysqld]下加上: default-storage-engine=INNODB 其中的蓝色字体是