概述

MySQL的XA事务支持包括内部XA事务和外部XA事务。内部XA事务主要指单节点实例内部,一个事务跨多个存储引擎进行读写,那么就会产生内部XA事务;这里需要指出的是,MySQL内部每个事务都需要写binlog,并且需要保证binlog与引擎修改的一致性,因此binlog是一个特殊的参与者,所以在打开binlog的情况下,即使事务修改只涉及一个引擎,内部也会启动XA事务。外部XA事务与内部XA事务核心逻辑类似,提供给用户一套XA事务的操作命令,包括XA start, XA end,XA prepre和XA commit等,可以支持跨多个节点的XA事务。外部XA的协调者是用户的应用,参与者是MySQL节点,因此需要应用持久化协调信息,解决事务一致性问题。无论外部XA事务还是内部XA事务,存储引擎实现的prepare和commit接口都是同一条路径,本文重点介绍内部XA事务。

协调者

MySQL内部XA事务,存储引擎是参与者,而协调者则有3个选项,包括binlog,TC_LOG_MMAP和TC_LOG_DUMMY。如果开启binlog,由于每个事务至少涉及一个存储引擎的修改,加上binlog,所以也会走XA事务流程。如果关闭binlog,事务修改涉及多个存储引擎,比如innodb和xengine引擎,那么内部会采用tc_log_map作为协调者。如果关闭binlog,且修改只涉及一个引擎innodb,那么实际上就不是XA事务,mysql内部为了保证接口统一,仍然使用了一个特殊的协调者TC_LOG_DUMMY,TC_LOG_DUMMY实际上什么也没做,只是做简单的转发,将server层的调用路由到引擎层调用,仅此而已。

协调者逻辑

  1. binlog作为协调者:
  2. prepareha_prepare_low
  3. commit write-binlog + ha_comit_low
  4. tclog作为协调者:
  5. prepareha_prepare_low
  6. commitwrtie-xid + ha_commit_low
  7. tc_dummy作为协调者:
  8. prepareha_prepare_low
  9. commitha_commit_low
  10. //是否支持2PC,是否修改超过了1个以上的引擎
  11. if (!trn_ctx->no_2pc(trx_scope) && (trn_ctx->rw_ha_count(trx_scope) > 1))
  12. error = tc_log->prepare(thd, all);

TC_LOG_MMAP和binlog作为协调者本质是相同的,就是在涉及跨引擎事务时,走2PC事务提交流程,分别调用引擎的prepare接口和commit接口。协调者如何确认是否走2PC逻辑,这里主要根据事务修改是否涉及多个引擎,特殊的是,如果打开binlog,binlog也会作为参与者考虑在内,最终统计事务涉及修改的参与者是否超过1,如果超过1,则进行2PC提交流程(prepare,commit)。注意,这里有一个前提条件是涉及的修改引擎必需都支持2PC。

  1. prepare逻辑:
  2. ha_prepare(bool prepare_tx) 这里的prepare_tx由外面传递的all=true/false决定。
  3. if (prepare_tx || (!my_core::thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) {
  4. tx->prepare
  5. }
  6. commit逻辑:
  7. if (commit_tx || (!my_core::thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) {
  8. tx->commit
  9. }

XA事务存储引擎接口

Server层与引擎层交互

从前面协调者逻辑我们了解到,MySQL内部XA事务,协调者在Server层,参与者在引擎层,因此Server层和引擎层需要有一定的通信机制来确定是否要进行2PC提交。这里主要包括两方面,一个是,事务涉及到的引擎要注册到协调者的事务列表中,二是,如果引擎有修改,要将已修改的信息通知给协调者。在MySQL中主要通过两个接口来实现,xengine_register_tx/innodbase_register_tx注册事务,handler::mark_trx_read_write标记事务读写。

注册事务路径

server根据需要访问表进行注册事务。

  1. mysql_lock_tables
  2. lock_external
  3. handler::ha_external_lock
  4. ha_innobase::external_lock
  5. innobase_register_trx
  6. trans_register_ha
  7. Transaction_ctx::set_ha_trx_info
  8. void xengine_register_tx(handlerton *const hton, THD *const thd,
  9. Xdb_transaction *const tx) {
  10. DBUG_ASSERT(tx != nullptr);
  11. //注册stmt的trx信息
  12. trans_register_ha(thd, FALSE, xengine_hton, NULL);
  13. if (my_core::thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) {
  14. tx->start_stmt();
  15. trans_register_ha(thd, TRUE, xengine_hton, NULL);
  16. }

标记事务修改

DDL事务

对于ddl事务,由于涉及到字典的多次修改,为了避免中途提交,临时将自动提交关闭。

  1. /**
  2. Check if statement (typically DDL) needs auto-commit mode temporarily
  3. turned off.
  4. @note This is necessary to prevent InnoDB from automatically committing
  5. InnoDB transaction each time data-dictionary tables are closed
  6. after being updated.
  7. */
  8. static bool sqlcom_needs_autocommit_off(const LEX *lex) {
  9. return (sql_command_flags[lex->sql_command] & CF_NEEDS_AUTOCOMMIT_OFF) ||
  10. (lex->sql_command == SQLCOM_CREATE_TABLE &&
  11. !(lex->create_info->options & HA_LEX_CREATE_TMP_TABLE)) ||
  12. (lex->sql_command == SQLCOM_DROP_TABLE && !lex->drop_temporary);
  13. }
  14. /*
  15. For statements which need this, prevent InnoDB from automatically
  16. committing InnoDB transaction each time data-dictionary tables are
  17. closed after being updated.
  18. */
  19. Disable_autocommit_guard(THD *thd) {
  20. m_thd->variables.option_bits &= ~OPTION_AUTOCOMMIT;

ddl注册事务

ddl标记事务修改

除了主动在server层注册事务,还需要主动将事务标记为read-write,标识这个ddl中xengine引擎有修改,这样server层在统计修改的事务引擎数时,会将xengine计算在內,最后再抉择是采用1PC事务提交还是2PC事务提交。目前,实际上在handler层的所有ddl路径,都主动调用了接口mark_trx_read_write,但由于在之前,并没有将引擎注册到server,导致整个调用对部分DDL操作无效。

这里考虑不开binlog的场景,因为开binlog情况下,任何一个事务只要有更新,加上binlog就会走内部XA事务。不开binlog场景下,如果同时启用xengine和innodb引擎,根据事务实际情况,可能会走到2PC流程。

| 1 | 场景 | 类别 | 是否走2PC流程 | 备注 | | — | — | — | — | — |
| 2 | (one-stmt)+(modify xengine) | DML事务 | no | 隐式事务,autocommit=on,单语句自动提交事务|
| 3 | (one-stmt)(modify xengine+innodb) | | yes | 隐式事务,autocommit=on,单语句自动提交事务 |
| 4 | (multi-stmt)+(modify xengine) | | no | 显示事务, 结合begin/commit |
| 5 | (multi-stmt)+(modify xengine+innodb) | | yes | 显示事务, 结合begin/commit |
| 6 | create table | DDL事务 | yes | storage engine mark_read_write |
| 7 | drop table | | yes | storage engine mark_read_write |
| 8 | rename table | | yes | storage engine mark_read_write |
| 9 | alter table online | | yes | |
| 10 | alter table copy-offline | | yes | |
说明,目前tc_log作为协调者,对于双引擎XA事务在部分路径存在问题。比如,对于场景3,应该走2PC流程没有走;对于场景4,不需要走2PC流程的场景反而走了2PC。