共计 9222 个字符,预计需要花费 24 分钟才能阅读完成。
一、只读事物
也许有人要问一个 select 算不算一个事物。其实在 innodb 中一个 innodb 的 select 是一个事物,他有 trx_t 结构体,并且放到了 MySQL_trx_list 链表中,关于
innodb 事物系统一级的事都做了,但是这种事物叫做只读事物
bool read_only; /*!< true if transaction is flagged
as a READ-ONLY transaction.
if auto_commit && will_lock == 0
then it will be handled as a
AC-NL-RO-SELECT (Auto Commit Non-Locking
Read Only Select). A read only
transaction will not be assigned an
UNDO log. */
在实际的使用中他没有自己的锁结构也没有自己的 undo segment,这一点很好理解因为这个操作
始终是非锁定的,至少在 innodb 一级是这样 (lock0lock.cc lock_table 都没调用),但是在 MYSQL 中,我们会发现实际上 select 语句也会
获得 MDL LOCK。(再次声明这里只是说 innodb select 没有表级别锁存在,但是 MYSQL 上层会有 MDL LOCK)
对于只读事物源码注释给出的流程如下:
Auto-commit non-locking read-only:
* NOT_STARTED -> ACTIVE -> NOT_STARTED
而我们一般的 2pc TRX 流程如下:
XA (2PC):
* NOT_STARTED -> ACTIVE -> PREPARED -> COMMITTED -> NOT_STARTED
可以看到他实际上就是没有 commit 的步骤,没有 undo reodo 这些当然是不需要的。但是不可否认它是一个事物
另外当需要一个事物的时候在现在 innodb 版本中调用如下:
trx_allocate_for_mysql –> trx_allocate_for_background –> trx_create_low
这里涉及到一个 innodb 事物池的概念,trx_create_low 从事物池中取出一个事物 TRX_T 结构体指针给调用者
这个步骤完成后事物处于 NOT_STARTED 阶段,这个时候 TRX_T 结构各种属性都处于初始化阶段,为什么要说一下
事物池的概念因为后面说事物号分配的时候会用到这个概念。
然后根据调用者的需求适时激活事物。实际上会调用,而调用会通过
trx_start_if_not_started_low->trx_start_low 完成,在 trx_start_low 做好事物结构的准备工作,我们来看一
下关于源码中重点的部分
trx->read_only =
(trx->api_trx && !trx->read_write)
|| (!trx->ddl && !trx->internal
&& thd_trx_is_read_only(trx->mysql_thd))
|| srv_read_only_mode; // 此处获取事物当前是否是只读属性,可以看到他和我们的 read_only 参数设置事物是 ddl 事物是否是内部事物有关
if (!trx->auto_commit) {// 是否自动提交否则需要设置 will_Lock 属性如果时候只读事物未 TURE,如果是 DML 事物为 flase
// 这里的 auto_commit 属性和我们平时设置的参数感觉不是一回事
++trx->will_lock;
} else if (trx->will_lock == 0) {
trx->read_only = true; // 如果不需要 will_lock 属性它肯定是只读事物
}
// 以上也就说明了只读事物不需要锁结构因为 trx->will_lock = 0(false)
/* We tend to over assert and that complicates the code somewhat.
e.g., the transaction state can be set earlier but we are forced to
set it under the protection of the trx_sys_t::mutex because some
trx list assertions are triggered unnecessarily. */
/* By default all transactions are in the read-only list unless they
are non-locking auto-commit read only transactions or background
(internal) transactions. Note: Transactions marked explicitly as
read only can write to temporary tables, we put those on the RO
list too. */
// 当然如果是非只读事物 我们需要开始分配 undo rollback segment 了 以及 undo segment 了
// 并且 trx->mysql_thd == 0 表示是否是 MYSQL 线程建立的 innodb 事物
// 是否是读写事物这个是由调用者传入只读事物为 false,DML 事物为 true,这里的读写和前面
//trx->read_only 有区别如果是只读事物建立临时表也是读写事物
// 是否是 DDL 事物 DDL 也需要分配 undo rollback segment 了 以及 undo segment
if (!trx->read_only
&& (trx->mysql_thd == 0 || read_write || trx->ddl)) {
trx->rsegs.m_redo.rseg = trx_assign_rseg_low(
srv_undo_logs, srv_undo_tablespaces,
TRX_RSEG_TYPE_REDO);
/* Temporary rseg is assigned only if the transaction
updates a temporary table */
trx_sys_mutex_enter();
trx_assign_id_for_rw(trx);// 分配事物号
/*
(gdb) p trx_sys->max_trx_id
$21 = 328707
*/
trx_sys_rw_trx_add(trx); // 将入集合
ut_ad(trx->rsegs.m_redo.rseg != 0
|| srv_read_only_mode
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
UT_LIST_ADD_FIRST(trx_sys->rw_trx_list, trx); // 将事物放入 rw_trx_list
ut_d(trx->in_rw_trx_list = true);
#ifdef UNIV_DEBUG
if (trx->id > trx_sys->rw_max_trx_id) {
trx_sys->rw_max_trx_id = trx->id;
}
#endif /* UNIV_DEBUG */
trx->state = TRX_STATE_ACTIVE; // 更改事物的状态为 ACTIVE
ut_ad(trx_sys_validate_trx_list());
trx_sys_mutex_exit();
} else {
trx->id = 0; // 任然没有分配事物 ID 给只读事物
if (!trx_is_autocommit_non_locking(trx)) {//#define trx_is_autocommit_non_locking(t) ((t)->auto_commit && (t)->will_lock == 0)
/* If this is a read-only transaction that is writing
to a temporary table then it needs a transaction id
to write to the temporary table. */
// 如果是只读事物并且写入了临时表需要额外操作
if (read_write) {
trx_sys_mutex_enter();
ut_ad(!srv_read_only_mode);
trx_assign_id_for_rw(trx);
trx_sys->rw_trx_set.insert(
TrxTrack(trx->id, trx));
trx_sys_mutex_exit();
}
trx->state = TRX_STATE_ACTIVE;
} else {
ut_ad(!read_write);
trx->state = TRX_STATE_ACTIVE;
}
}
if (trx->mysql_thd != NULL) {
trx->start_time = thd_start_time_in_secs(trx->mysql_thd); // 开始计时这是系统时间 LINUX 系统调用 time()
} else {
trx->start_time = ut_time();
}
根据上面的注释,我们可以看到只读事物没有分配 undo segment 也不会分配 LOCK 锁结构
二、事物 ID 的分配
也许很多朋友不止我一个人在 show engine innodb status 的时候会看到如下两种截然不同,相差很大的事物 ID
(MYSQL)—TRANSACTION 329759, ACTIVE 10 sec
1 lock struct(s), heap size 1160, 0 row lock(s), undo log entries 1
MySQL thread id 3, OS thread handle 140737154152192, query id 28 localhost root cleaning up
(MYSQL)—TRANSACTION 422212177398528, not started
0 lock struct(s), heap size 1160, 0 row lock(s)
这里事物 id 329759 和 422212177398528 相差很大,innodb 是怎么分配的呢?
其实这里实际上的事物 id 只有 329759,及 trx_t.id, 是一个正常的 DML 事物,而对于 not started 状态的事物
以及只读事物,是没有事物 id 的其实就是 0,但是 show engine innodb status 的时候会调用时候会简单的分配
一个而已。
其实 TRX ID 的分配也是在 trx_start_low 中调用 (trx_assign_id_for_rw(trx);// 分配事物号 )
而对于只读事物并不会分配 (trx->id = 0; // 任然没有分配事物 ID 给只读事物) 都在上面的代码解释中,
而 show engine innodb status 的时候对于事物 ID 为 0 的事物做如下输出
UNIV_INLINE
trx_id_t
trx_get_id_for_print(
const trx_t* trx)
{
/* Readonly and transactions whose intentions are unknown (whether
they will eventually do a WRITE) don’t have trx_t::id assigned (it is
0 for those transactions). Transaction IDs in
innodb_trx.trx_id,
innodb_locks.lock_id,
innodb_locks.lock_trx_id,
innodb_lock_waits.requesting_trx_id,
innodb_lock_waits.blocking_trx_id should match because those tables
could be used in an SQL JOIN on those columns. Also trx_t::id is
printed by SHOW ENGINE INNODB STATUS, and in logs, so we must have the
same value printed everywhere consistently. */
/* DATA_TRX_ID_LEN is the storage size in bytes. */
static const trx_id_t max_trx_id
= (1ULL << (DATA_TRX_ID_LEN * CHAR_BIT)) – 1;
ut_ad(trx->id <= max_trx_id);
return(trx->id != 0
? trx->id
: reinterpret_cast<trx_id_t>(trx) | (max_trx_id + 1));
}
我们从注释也能看出
Readonly and transactions whose intentions are unknown don’t have trx_t::id assigned (it is 0 for those transactions)
实际上 422212177398528 这种 id 就是这里打印的时候分配的,没有什么实际的意义
这里的 max_trx_id 是一个常量 281474976710655 如果 trx->id== 0 会调用
reinterpret_cast(trx) | (max_trx_id + 1)
将指针转换为一个 64 字节非负整数然后位或上(max_trx_id + 1),如下:
(gdb) p max_trx_id
$19 = 281474976710655
(gdb) p reinterpret_cast(trx)
$20 = 140737200690640
(gdb) p reinterpret_cast(trx) | (max_trx_id + 1)
$21 = 422212177401296
而对于这里 DML 的事物号的分配如下:
void
trx_assign_id_for_rw(trx_t* trx)
{
ut_ad(mutex_own(&trx_sys->mutex));
trx->id = trx->preallocated_id
? trx->preallocated_id : trx_sys_get_new_trx_id();
// 先判断是否是这个事物分配过事物 ID,因为从事物池中拿出来
// 很可能以前用过,那么就不需要再次分配了,否则新分配
if (trx->preallocated_id) {// 如果是以前使用过的不一定是最大需要加入到 vertor 中间
// Maintain ordering in rw_trx_ids
trx_sys->rw_trx_ids.insert(
std::upper_bound(trx_sys->rw_trx_ids.begin(),
trx_sys->rw_trx_ids.end(),
trx->id), trx->id);
} else {
// The id is known to be greatest 新分配的肯定是最大 如果是最大加到某位即可
trx_sys->rw_trx_ids.push_back(trx->id);
}
}
这里涉及到事物池。
而对于 trx_sys_get_new_trx_id 如下:
trx_sys_get_new_trx_id()
/*====================*/
{
ut_ad(trx_sys_mutex_own());
/* VERY important: after the database is started, max_trx_id value is
divisible by TRX_SYS_TRX_ID_WRITE_MARGIN, and the following if
will evaluate to TRUE when this function is first time called,
and the value for trx id will be written to disk-based
Thus trx id values will not overlap when the database is
repeatedly */
if (!(trx_sys->max_trx_id % TRX_SYS_TRX_ID_WRITE_MARGIN)) {
trx_sys_flush_max_trx_id(); //TRX_SYS_TRX_ID_WRITE_MARGIN 为 256 如果 trx_sys->max_trx_id 达到 256 的整数倍需要刷盘
// 到 TRX_SYS_TRX_ID_STORE 中.
}
return(trx_sys->max_trx_id++);// 然后自身 + 1 返回
}
如此我们看到 DML 事物的事物 ID 是 innodb 分配的,而只读事物或者 not start 事物的事物 ID 是在 show engine 的时候根据 trx_t 结构体
所在内存的指针算法出来的,没有实际的意义。
三、验证只读事物的存在
对于只读事物我们在 show engine innodb 只会打印出 not start 的事物或者活跃的已经获得了锁结构的事物一般是 DML 操作
但是可以再 innodb_trx 中观察到,我这里就简单修改 show engine innodb 源码打印输出将只读事物打印出来标记为 RO TRX,
并且和 innodb_trx 对比
下面是我修改后 show engine innodb 的输出
LIST OF TRANSACTIONS FOR EACH SESSION(1)(CHANGE BY GAOPENG ALL mysql_trx_list and rw_trx_list):
(MYSQL)—TRANSACTION 422212177402680, ACTIVE 3 sec fetching rows
mysql tables in use 1, locked 0
0 lock struct(s), heap size 1160, 0 row lock(s), RO TRX
MySQL thread id 7, OS thread handle 140737153619712, query id 411 localhost root Sending data
select * from test.tuser
这里看到我们的只读事物为 RO TRX,lock struct(s)为 0,没有 undo entries,因为有会打印出来。
再来看看 innodb_trx 的输出:
mysql> select * from information_schema.innodb_trx \G
*************************** 1. row ***************************
trx_id: 422212177402680
trx_state: RUNNING
trx_started: 2017-07-19 16:52:53
trx_requested_lock_id: NULL
trx_wait_started: NULL
trx_weight: 0
trx_mysql_thread_id: 7
trx_query: select * from test.tuser
trx_operation_state: fetching rows
trx_tables_in_use: 1
trx_tables_locked: 0
trx_lock_structs: 0
trx_lock_memory_bytes: 1160
trx_rows_locked: 0
trx_rows_modified: 0
trx_concurrency_tickets: 0
trx_isolation_level: REPEATABLE READ
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 0
trx_is_read_only: 1
trx_autocommit_non_locking: 1
没有问题都能观察到,同样我们也如我们所说只读事物的事物 ID 是 422212177402680,只是 TRX_T 结构体指针所在位置算出来的
算法在上面。这里注意事物也是有状态标识的比如这里的 fetching rows。
四、其他
其实 innodb 中的事物比想象的要大很多,一个 innodb 的 ddl 是一个事物,一个 innodb 的 select 是一个事物,很多内部修改数据字典的操作也是一个事物
当然我们平时做的 DML 那更是事物了,上面说了只读事物这里简单提一下 ddl 事物和内部事物。
这里将 trx_t 结构体重关于他们标志给出来:
innodb 的 ddl 事物:
bool ddl; /*!< true if it is an internal transaction for DDL */
函数调用:trx_start_for_ddl_low–>trx_start_internal_low
可以看到一个 ddl 既是一个内部事物也是一个 ddl 事物
innodb 的内部事物:
bool internal; /*!< true if it is a system/internal
transaction background task. This
includes DDL transactions too. Such
transactions are always treated as
read-write. */
函数调用:trx_start_internal_low 典型的 innodb 修改数据字典就是 internal 事物
关于只读事物实际上在官方手册也有说明具体在
Optimizing InnoDB Read-Only Transactions
我就不再说明什么了。
本文永久更新链接地址:http://www.linuxidc.com/Linux/2017-07/145800.htm