Author: yuanzhen
在学习代码的过程中经常看到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 类型定义如下:
/**
Class representing read-only attachable transaction, encapsulates
knowledge how to backup state of current transaction, start
read-only attachable transaction in SE, finalize it and then restore
state of original transaction back. Also serves as a base class for
read-write attachable transaction implementation.
*/
class Attachable_trx
{
public:
Attachable_trx(THD *thd);
virtual ~Attachable_trx();
virtual bool is_read_only() const { return true; }
protected:
/// THD instance.
THD *m_thd;
/// Transaction state data.
Transaction_state m_trx_state;
private:
Attachable_trx(const Attachable_trx &);
Attachable_trx &operator =(const Attachable_trx &);
};
其中最重要的是m_trx_state字段,期存放着attachable transaction的重要信息,就是用它来保存外部用户事务的状态,以便在着attachable transaction结束后能恢复到原来的用户事务状态。其定义如下:
/** An utility struct for @c Attachable_trx */
struct Transaction_state
{
void backup(THD *thd);
void restore(THD *thd);
/// SQL-command.
enum_sql_command m_sql_command;
Query_tables_list m_query_tables_list;
/// Open-tables state.
Open_tables_backup m_open_tables_state;
/// SQL_MODE.
sql_mode_t m_sql_mode;
/// Transaction isolation level.
enum_tx_isolation m_tx_isolation;
/// Ha_data array.
Ha_data m_ha_data[MAX_HA];
/// Transaction_ctx instance.
Transaction_ctx *m_trx;
/// Transaction read-only state.
my_bool m_tx_read_only;
/// THD options.
ulonglong m_thd_option_bits;
/// Current transaction instrumentation.
PSI_transaction_locker *m_transaction_psi;
/// Server status flags.
uint m_server_status;
};
主要有这几个函数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层。
THD::Attachable_trx::Attachable_trx(THD *thd)
:m_thd(thd)
{
m_trx_state.backup(m_thd); //保存当前已经开始的用户的正常事务状态
......
m_thd->reset_n_backup_open_tables_state(&m_trx_state.m_open_tables_state); //保存一些打开表的状态信息,并且重新为新的事物重置表状态
// 为attachable transaction创建一个新的事物上下文
m_thd->m_transaction.release(); // it's been backed up.
m_thd->m_transaction.reset(new Transaction_ctx());
......
for (int i= 0; i < MAX_HA; ++i)
m_thd->ha_data[i]= Ha_data(); //重新设置THD::ha_data
m_thd->tx_isolation= ISO_READ_COMMITTED; // attachable transaction 必须是read committed
m_thd->tx_read_only= true; // attachable transaction 必须是只读的
// attachable transaction 必须是 AUTOCOMMIT
m_thd->variables.option_bits|= OPTION_AUTOCOMMIT;
m_thd->variables.option_bits&= ~OPTION_NOT_AUTOCOMMIT;
m_thd->variables.option_bits&= ~OPTION_BEGIN;
......
}
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()接口。
THD::Attachable_trx::~Attachable_trx()
{
......
close_thread_tables(m_thd); //调用close_thread_tables()关闭在attachable transaction中打开的
ha_close_connection(m_thd); //调用close_connection()通知引擎层去销毁为attachable transaction创建的事务
// 恢复之前正常的用户事状态
m_trx_state.restore(m_thd);
m_thd->restore_backup_open_tables_state(&m_trx_state.m_open_tables_state);
......
}
由于一个会话同时只允许有一个活跃事务,当需要访问内部的事务系统表时,就需要开启一个Attachable transaction事务,这时就要先把外部的主事务状态先保存起来,等内部开启的Attachable transaction事务执行完,再把外部用户执行的事务恢复回来。为了实现这个功能,提供了backup和restore的接口。
为了让存储层知道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 标志的代码如下:
ha_innobase::ha_innobase(
/*=====================*/
handlerton* hton,
TABLE_SHARE* table_arg)
:handler(hton, table_arg),
m_prebuilt(),
m_prebuilt_ptr(&m_prebuilt),
m_user_thd(),
m_int_table_flags(HA_REC_NOT_IN_SEQ
......
| HA_ATTACHABLE_TRX_COMPATIBLE
| HA_CAN_INDEX_VIRTUAL_GENERATED_COLUMN
),
m_start_of_scan(),
m_num_write_row(),
m_mysql_has_locked()
{}