原创水平有限,如果有误请指出
一、只读事物
也许有人要问一个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的事物做如下输出
我们从注释也能看出
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的事物号的分配如下:
这里涉及到事物池。
而对于trx_sys_get_new_trx_id如下:
如此我们看到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对比
这里看到我们的只读事物为RO TRX,lock struct(s)为0,没有undo entries,因为有会打印出来。
再来看看innodb_trx的输出:
没有问题都能观察到,同样我们也如我们所说只读事物的事物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事物
关于只读事物实际上在官方手册也有说明具体在
作者微信:
一、只读事物
也许有人要问一个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
再来看看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
算法在上面。这里注意事物也是有状态标识的比如这里的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
我就不在说明什么了。作者微信: