目的

在学习代码的过程中经常看到attachable transaction,它到底是做什么的,目的是什么呢。这篇文章简单的介绍一下它的作用和用法,以帮助大家理解代码。

简介

Attachable transaction是从5.7引入的一个概念,主要用来对事务类型的系统表访问的接口,从事务的系统表查询得到一致的数据。目前主要是对innodb类型的系统表访问的接口,也只有innodb引擎实现了attachable transaction的支持。Attachable transaction 主要是为访问事务类型的系统表而设计的,它是一个嵌入用户事务的内部事务,当用户事务用到元信息时就需要开启一个attachable transaction去访问数据字典,得到用户表的元信息后,要结束attachable transaction,用户会话要能恢复到用户事务之前的状态。 Attachable transaction 是一个AC-RO-RC-NL (auto-commit, read-only, read-committed, non-locking) 事务。引入Attachable transaction主要有以下几个原因: 1) 如果用户开启的一个事务需要访问系统表获取表的元信息,而访问系统表可能和用户事务指定的隔离级别不一致,这时就需要开启一个独立的访问数据字典的事务,要求访问数据字典事务的隔离级别必须是READ COMMITTED,其隔离级别可能和用户指定的隔离级别不一致。 2) 对数据字典的访问必须是非锁定的。 3) 即时用户事务已经打开和锁定了用户表,在执行SQL语句的在任何时候也应该能对数据字典打开,来查询用户表的各种元信息。

核心数据结构

在每个会话的THD结构里,添加了一个 Attachable_trx *m_attachable_trx;字段,用来指向当前会话的内嵌事务。Attachable_trx 类型定义如下:

  1. /**
  2. Class representing read-only attachable transaction, encapsulates
  3. knowledge how to backup state of current transaction, start
  4. read-only attachable transaction in SE, finalize it and then restore
  5. state of original transaction back. Also serves as a base class for
  6. read-write attachable transaction implementation.
  7. */
  8. class Attachable_trx
  9. {
  10. public:
  11. Attachable_trx(THD *thd);
  12. virtual ~Attachable_trx();
  13. virtual bool is_read_only() const { return true; }
  14. protected:
  15. /// THD instance.
  16. THD *m_thd;
  17. /// Transaction state data.
  18. Transaction_state m_trx_state;
  19. private:
  20. Attachable_trx(const Attachable_trx &);
  21. Attachable_trx &operator =(const Attachable_trx &);
  22. };

其中最重要的是m_trx_state字段,期存放着attachable transaction的重要信息,就是用它来保存外部用户事务的状态,以便在着attachable transaction结束后能恢复到原来的用户事务状态。其定义如下:

  1. /** An utility struct for @c Attachable_trx */
  2. struct Transaction_state
  3. {
  4. void backup(THD *thd);
  5. void restore(THD *thd);
  6. /// SQL-command.
  7. enum_sql_command m_sql_command;
  8. Query_tables_list m_query_tables_list;
  9. /// Open-tables state.
  10. Open_tables_backup m_open_tables_state;
  11. /// SQL_MODE.
  12. sql_mode_t m_sql_mode;
  13. /// Transaction isolation level.
  14. enum_tx_isolation m_tx_isolation;
  15. /// Ha_data array.
  16. Ha_data m_ha_data[MAX_HA];
  17. /// Transaction_ctx instance.
  18. Transaction_ctx *m_trx;
  19. /// Transaction read-only state.
  20. my_bool m_tx_read_only;
  21. /// THD options.
  22. ulonglong m_thd_option_bits;
  23. /// Current transaction instrumentation.
  24. PSI_transaction_locker *m_transaction_psi;
  25. /// Server status flags.
  26. uint m_server_status;
  27. };

核心接口API

启动一个attachable transaction

主要有这几个函数THD::begin_attachable_transaction()/begin_attachable_ro_transaction()/begin_attachable_rw_transaction(), 启动attachable transaction的过程中主要完成以下功能: 1) 在开始一个attachable_transaction之前,先要保存当前已经开始的用户的正常事务状态。 2) 开始设置一个新的事务所需要的各种状态。 3) 重新设置THD::ha_data。通过重制THD::ha_data值使InnoDB在接下来的操作去创建以下新的事务。 4) 执行对系统表的操作。 5) 当执行到存储引擎层时,InnoDB从传进来的THD指针中发现事务还没开启 (因为THD::ha_data重置了),InnoDB就会新启一个事务。 6) InnoDB通过调用trans_register_ha()通知server它已经创建了一个新事务。 7) InnoDB执行请求的操作,返回给server层。

  1. THD::Attachable_trx::Attachable_trx(THD *thd)
  2. :m_thd(thd)
  3. {
  4. m_trx_state.backup(m_thd); //保存当前已经开始的用户的正常事务状态
  5. ......
  6. m_thd->reset_n_backup_open_tables_state(&m_trx_state.m_open_tables_state); //保存一些打开表的状态信息,并且重新为新的事物重置表状态
  7. // 为attachable transaction创建一个新的事物上下文
  8. m_thd->m_transaction.release(); // it's been backed up.
  9. m_thd->m_transaction.reset(new Transaction_ctx());
  10. ......
  11. for (int i= 0; i < MAX_HA; ++i)
  12. m_thd->ha_data[i]= Ha_data(); //重新设置THD::ha_data
  13. m_thd->tx_isolation= ISO_READ_COMMITTED; // attachable transaction 必须是read committed
  14. m_thd->tx_read_only= true; // attachable transaction 必须是只读的
  15. // attachable transaction 必须是 AUTOCOMMIT
  16. m_thd->variables.option_bits|= OPTION_AUTOCOMMIT;
  17. m_thd->variables.option_bits&= ~OPTION_NOT_AUTOCOMMIT;
  18. m_thd->variables.option_bits&= ~OPTION_BEGIN;
  19. ......
  20. }

结束一个attachable transaction

THD::end_attachable_transaction()函数。因为attachable transaction事务是一个只读的自提交事务,所以它不需要调用任何事务需要提交火回滚的函数,比如: ha_commit_trans() / ha_rollback_trans() / trans_commit() / trans_rollback()。所以定义了此函数用来结束当前的attachable transaction。它主要完成以下功能: 1) 调用close_thread_tables()关闭在attachable transaction中打开的表。 2) 调用close_connection()通知引擎层去销毁为attachable transaction创建的事务。 3) InnoDB调用trx_commit_in_memory()去销毁readview等操作。 4) 最后要恢复之前正常的用户事务,包括THD::ha_data的恢复,这个通过调用下面提到的事务状态的backup()/restore()接口。

  1. THD::Attachable_trx::~Attachable_trx()
  2. {
  3. ......
  4. close_thread_tables(m_thd); //调用close_thread_tables()关闭在attachable transaction中打开的
  5. ha_close_connection(m_thd); //调用close_connection()通知引擎层去销毁为attachable transaction创建的事务
  6. // 恢复之前正常的用户事状态
  7. m_trx_state.restore(m_thd);
  8. m_thd->restore_backup_open_tables_state(&m_trx_state.m_open_tables_state);
  9. ......
  10. }

事务的保存、恢复接口函数 Transaction_state::backup()/restore()

由于一个会话同时只允许有一个活跃事务,当需要访问内部的事务系统表时,就需要开启一个Attachable transaction事务,这时就要先把外部的主事务状态先保存起来,等内部开启的Attachable transaction事务执行完,再把外部用户执行的事务恢复回来。为了实现这个功能,提供了backup和restore的接口。

Handler API的改动

为了让存储层知道server层开启的是一个attachable transaction,handler API新加了一个HA_ATTACHABLE_TRX_COMPATIBLE 标志。设置这个标志的存储引擎类型表示引擎层已经意识到开启了attachable transaction的事务类型。目前InnoDB和MyISAM引擎都能处理这种attachable transaction。但处理方式不同: 1)对InnoDB而言,完全支持attachable transaction事务,能够感知到THD::ha_data 的变化并开启一个attachable transaction事务。在close_connection时结束一个attachable transaction事务,然后恢复用户正常的事务继续处理。 2) 对MyISAM而言,虽然知道server开启了一个attachable transaction事务但也不做任何处理,就是简单但忽律掉 THD::ha_data and close_connection handlerton相关但处理。

在初始化一个InnoDB表时,设置HA_ATTACHABLE_TRX_COMPATIBLE 标志的代码如下:

  1. ha_innobase::ha_innobase(
  2. /*=====================*/
  3. handlerton* hton,
  4. TABLE_SHARE* table_arg)
  5. :handler(hton, table_arg),
  6. m_prebuilt(),
  7. m_prebuilt_ptr(&m_prebuilt),
  8. m_user_thd(),
  9. m_int_table_flags(HA_REC_NOT_IN_SEQ
  10. ......
  11. | HA_ATTACHABLE_TRX_COMPATIBLE
  12. | HA_CAN_INDEX_VIRTUAL_GENERATED_COLUMN
  13. ),
  14. m_start_of_scan(),
  15. m_num_write_row(),
  16. m_mysql_has_locked()
  17. {}