原创水平有限,如果有误请指出
一、只读事物
也许有人要问一个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做好事物结构的准备工作,我们来看一
下关于源码中重点的部分
根据上面的注释,我们可以看到只读事物没有分配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
我就不在说明什么了。
作者微信: