原创水平有限,如果有误请指出
一、只读事物
也许有人要问一个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
我就不在说明什么了。
作者微信:
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/7728585/viewspace-2142302/,如需转载,请注明出处,否则将追究法律责任。