MYSQL innodb中的只读事物以及事物id的分配方式

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 原创水平有限,如果有误请指出 一、只读事物 也许有人要问一个select算不算一个事物。其实在innodb中一个innodb的select是一个事物,他有trx_t结构体,并且放到了mysql_trx_list链表中,关于 innodb事物系统一级的事都做了,但...
原创水平有限,如果有误请指出


一、只读事物
也许有人要问一个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做好事物结构的准备工作,我们来看一
下关于源码中重点的部分


点击(此处)折叠或打开

  1. trx->read_only =
  2.         (trx->api_trx && !trx->read_write)
  3.         || (!trx->ddl && !trx->internal
  4.          && thd_trx_is_read_only(trx->mysql_thd))
  5.         || srv_read_only_mode; //此处获取事物当前是否是只读属性,可以看到他和我们的read_only参数设置事物是ddl事物是否是内部事物有关

  6.     if (!trx->auto_commit) { //是否自动提交否则需要设置will_Lock属性如果时候只读事物未TURE,如果是DML事物为flase
  7.      //这里的auto_commit属性和我们平时设置的参数感觉不是一回事
  8.         ++trx->will_lock;
  9.     } else if (trx->will_lock == 0) {
  10.         trx->read_only = true; //如果不需要will_lock属性它肯定是只读事物
  11.     }
  12. //以上也就说明了只读事物不需要锁结构因为 trx->will_lock = 0(false)
  13.     /* We tend to over assert and that complicates the code somewhat.
  14.     e.g., the transaction state can be set earlier but we are forced to
  15.     set it under the protection of the trx_sys_t::mutex because some
  16.     trx list assertions are triggered unnecessarily. */

  17.     /* By default all transactions are in the read-only list unless they
  18.     are non-locking auto-commit read only transactions or background
  19.     (internal) transactions. Note: Transactions marked explicitly as
  20.     read only can write to temporary tables, we put those on the RO
  21.     list too. */
  22.     //当然如果是非只读事物 我们需要开始分配undo rollback segment了 以及undo segment了
  23.     //并且trx->mysql_thd == 0 表示是否是MYSQL线程建立的innodb事物
  24.     //是否是读写事物这个是由调用者传入只读事物为false,DML事物为true,这里的读写和前面
  25.     //trx->read_only有区别如果是只读事物建立临时表也是读写事物
  26.     //是否是DDL事物 DDL也需要分配undo rollback segment了 以及undo segment
  27.     if (!trx->read_only
  28.      && (trx->mysql_thd == 0 || read_write || trx->ddl)) {

  29.         trx->rsegs.m_redo.rseg = trx_assign_rseg_low(
  30.             srv_undo_logs, srv_undo_tablespaces,
  31.             TRX_RSEG_TYPE_REDO);

  32.         /* Temporary rseg is assigned only if the transaction
  33.         updates a temporary table */

  34.         trx_sys_mutex_enter();

  35.         trx_assign_id_for_rw(trx);//分配事物号
  36.         /*
  37.      (gdb) p trx_sys->max_trx_id
  38.         $21 = 328707
  39.      */

  40.         trx_sys_rw_trx_add(trx); //将入集合

  41.         ut_ad(trx->rsegs.m_redo.rseg != 0
  42.          || srv_read_only_mode
  43.          || srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);

  44.         UT_LIST_ADD_FIRST(trx_sys->rw_trx_list, trx); //将事物放入rw_trx_list

  45.         ut_d(trx->in_rw_trx_list = true);
  46. #ifdef UNIV_DEBUG
  47.         if (trx->id > trx_sys->rw_max_trx_id) {
  48.             trx_sys->rw_max_trx_id = trx->id;
  49.         }
  50. #endif /* UNIV_DEBUG */

  51.         trx->state = TRX_STATE_ACTIVE; //更改事物的状态为ACTIVE

  52.         ut_ad(trx_sys_validate_trx_list());

  53.         trx_sys_mutex_exit();

  54.     } else {
  55.         trx->id = 0; //任然没有分配事物ID给只读事物

  56.         if (!trx_is_autocommit_non_locking(trx)) { //#define trx_is_autocommit_non_locking(t)     ((t)->auto_commit && (t)->will_lock == 0)

  57.             /* If this is a read-only transaction that is writing
  58.             to a temporary table then it needs a transaction id
  59.             to write to the temporary table. */
  60.             //如果是只读事物并且写入了临时表需要额外操作

  61.             if (read_write) {

  62.                 trx_sys_mutex_enter();

  63.                 ut_ad(!srv_read_only_mode);

  64.                 trx_assign_id_for_rw(trx);

  65.                 trx_sys->rw_trx_set.insert(
  66.                     TrxTrack(trx->id, trx));

  67.                 trx_sys_mutex_exit();
  68.             }

  69.             trx->state = TRX_STATE_ACTIVE;

  70.         } else {
  71.             ut_ad(!read_write);
  72.             trx->state = TRX_STATE_ACTIVE;
  73.         }
  74.     }

  75.     if (trx->mysql_thd != NULL) {
  76.         trx->start_time = thd_start_time_in_secs(trx->mysql_thd); //开始计时这是系统时间LINUX系统调用time()
  77.     } else {
  78.         trx->start_time = ut_time();
  79.     }

根据上面的注释,我们可以看到只读事物没有分配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的事物做如下输出


点击(此处)折叠或打开

  1. UNIV_INLINE
  2. trx_id_t
  3. trx_get_id_for_print(
  4.     const trx_t*    trx)
  5. {
  6.     /* Readonly and transactions whose intentions are unknown (whether
  7.     they will eventually do a WRITE) don't have trx_t::id assigned (it is
  8.     0 for those transactions). Transaction IDs in
  9.     innodb_trx.trx_id,
  10.     innodb_locks.lock_id,
  11.     innodb_locks.lock_trx_id,
  12.     innodb_lock_waits.requesting_trx_id,
  13.     innodb_lock_waits.blocking_trx_id should match because those tables
  14.     could be used in an SQL JOIN on those columns. Also trx_t::id is
  15.     printed by SHOW ENGINE INNODB STATUS, and in logs, so we must have the
  16.     same value printed everywhere consistently. */
  17.   
  18.     /* DATA_TRX_ID_LEN is the storage size in bytes. */
  19.     static const trx_id_t    max_trx_id
  20.         = (1ULL << (DATA_TRX_ID_LEN * CHAR_BIT)) - 1;

  21.     ut_ad(trx->id <= max_trx_id);

  22.     return(trx->id != 0
  23.      ? trx->id
  24.      : reinterpret_cast<trx_id_t>(trx) | (max_trx_id + 1));
  25. }

我们从注释也能看出 
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的事物号的分配如下:

点击(此处)折叠或打开

  1. void
  2. trx_assign_id_for_rw(trx_t* trx)
  3. {
  4.     ut_ad(mutex_own(&trx_sys->mutex));

  5.     trx->id = trx->preallocated_id
  6.         ? trx->preallocated_id : trx_sys_get_new_trx_id();
  7.     //先判断是否是这个事物分配过事物ID,因为从事物池中拿出来
  8.     //很可能以前用过,那么就不需要再次分配了,否则新分配

  9.     if (trx->preallocated_id) { //如果是以前使用过的不一定是最大需要加入到vertor中间
  10.         // Maintain ordering in rw_trx_ids
  11.         trx_sys->rw_trx_ids.insert(
  12.             std::upper_bound(trx_sys->rw_trx_ids.begin(),
  13.                      trx_sys->rw_trx_ids.end(),
  14.                      trx->id), trx->id);
  15.     } else {
  16.         // The id is known to be greatest 新分配的肯定是最大 如果是最大加到某位即可
  17.         trx_sys->rw_trx_ids.push_back(trx->id);
  18.     }
  19. }
这里涉及到事物池。
而对于trx_sys_get_new_trx_id如下:

点击(此处)折叠或打开

  1. trx_sys_get_new_trx_id()
  2. /*====================*/
  3. {
  4.     ut_ad(trx_sys_mutex_own());

  5.     /* VERY important: after the database is started, max_trx_id value is
  6.     divisible by TRX_SYS_TRX_ID_WRITE_MARGIN, and the following if
  7.     will evaluate to TRUE when this function is first time called,
  8.     and the value for trx id will be written to disk-based
  9.     Thus trx id values will not overlap when the database is
  10.     repeatedly */

  11.     if (!(trx_sys->max_trx_id % TRX_SYS_TRX_ID_WRITE_MARGIN)) {

  12.         trx_sys_flush_max_trx_id(); //TRX_SYS_TRX_ID_WRITE_MARGIN为256 如果trx_sys->max_trx_id达到256的整数倍需要刷盘
  13.          //到TRX_SYS_TRX_ID_STORE中.
  14.     }

  15.     return(trx_sys->max_trx_id++);//然后自身+1返回
  16. }

如此我们看到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对比

点击(此处)折叠或打开

  1. 下面是我修改后show engine innodb的输出

  2. LIST OF TRANSACTIONS FOR EACH SESSION(1)(CHANGE BY GAOPENG ALL mysql_trx_list and rw_trx_list):
  3. (MYSQL)---TRANSACTION 422212177402680, ACTIVE 3 sec fetching rows
  4. mysql tables in use 1, locked 0
  5. 0 lock struct(s), heap size 1160, 0 row lock(s), RO TRX
  6. MySQL thread id 7, OS thread handle 140737153619712, query id 411 localhost root Sending data
  7. select * from test.tuser
这里看到我们的只读事物为RO TRX,lock struct(s)为0,没有undo entries,因为有会打印出来。
再来看看innodb_trx的输出:

点击(此处)折叠或打开

  1. mysql> select * from information_schema.innodb_trx \G
  2. *************************** 1. row ***************************
  3.                     trx_id: 422212177402680
  4.                  trx_state: RUNNING
  5.                trx_started: 2017-07-19 16:52:53
  6.      trx_requested_lock_id: NULL
  7.           trx_wait_started: NULL
  8.                 trx_weight: 0
  9.        trx_mysql_thread_id: 7
  10.                  trx_query: select * from test.tuser
  11.        trx_operation_state: fetching rows
  12.          trx_tables_in_use: 1
  13.          trx_tables_locked: 0
  14.           trx_lock_structs: 0
  15.      trx_lock_memory_bytes: 1160
  16.            trx_rows_locked: 0
  17.          trx_rows_modified: 0
  18.    trx_concurrency_tickets: 0
  19.        trx_isolation_level: REPEATABLE READ
  20.          trx_unique_checks: 1
  21.     trx_foreign_key_checks: 1
  22. trx_last_foreign_key_error: NULL
  23.  trx_adaptive_hash_latched: 0
  24.  trx_adaptive_hash_timeout: 0
  25.           trx_is_read_only: 1
  26. 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 

我就不在说明什么了。


作者微信:

img_4166a896a28155d27307bf8bdad181d5.jpg


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
7天前
|
存储 关系型数据库 MySQL
Mysql索引:深入理解InnoDb聚集索引与MyisAm非聚集索引
通过本文的介绍,希望您能深入理解InnoDB聚集索引与MyISAM非聚集索引的概念、结构和应用场景,从而在实际工作中灵活运用这些知识,优化数据库性能。
46 7
|
1月前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
135 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
13天前
|
存储 关系型数据库 MySQL
MySQL引擎InnoDB和MyISAM的区别?
InnoDB是MySQL默认的事务型存储引擎,支持事务、行级锁、MVCC、在线热备份等特性,主索引为聚簇索引,适用于高并发、高可靠性的场景。MyISAM设计简单,支持压缩表、空间索引,但不支持事务和行级锁,适合读多写少、不要求事务的场景。
42 9
|
13天前
|
存储 关系型数据库 MySQL
InnoDB为什么使用自增id作为主键?
MySQL以数据页(默认16K)为单位存储数据。自增ID主键时,写满一页直接申请新页;非自增ID主键需保持索引有序,插入数据可能引发页分裂,即需将部分数据移至新页,影响插入效率。
28 6
|
1月前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的表空间
InnoDB是MySQL默认的存储引擎,主要由存储结构、内存结构和线程结构组成。其存储结构分为逻辑和物理两部分,逻辑存储结构包括表空间、段、区和页。表空间是InnoDB逻辑结构的最高层,所有数据都存放在其中。默认情况下,InnoDB有一个共享表空间ibdata1,用于存放撤销信息、系统事务信息等。启用参数`innodb_file_per_table`后,每张表的数据可以单独存放在一个表空间内,但撤销信息等仍存放在共享表空间中。
|
1月前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的段、区和页
MySQL的InnoDB存储引擎逻辑存储结构与Oracle相似,包括表空间、段、区和页。表空间由段和页组成,段包括数据段、索引段等。区是1MB的连续空间,页是16KB的最小物理存储单位。InnoDB是面向行的存储引擎,每个页最多可存放7992行记录。
|
1月前
|
监控 关系型数据库 MySQL
MySQL自增ID耗尽应对策略:技术解决方案全解析
在数据库管理中,MySQL的自增ID(AUTO_INCREMENT)属性为表中的每一行提供了一个唯一的标识符。然而,当自增ID达到其最大值时,如何处理这一情况成为了数据库管理员和开发者必须面对的问题。本文将探讨MySQL自增ID耗尽的原因、影响以及有效的应对策略。
104 3
|
1月前
|
存储 监控 关系型数据库
MySQL自增ID耗尽解决方案:应对策略与实践技巧
在MySQL数据库中,自增ID(AUTO_INCREMENT)是一种特殊的属性,用于自动为新插入的行生成唯一的标识符。然而,当自增ID达到其最大值时,会发生什么?又该如何解决?本文将探讨MySQL自增ID耗尽的问题,并提供一些实用的解决方案。
38 1
|
关系型数据库 数据库 PostgreSQL
RDS for PostgreSQL无法建只读用户的问题讨论
在RDS for PostgreSQL中,任何一个用户都能建表,而无法禁止掉这个权限的问题。
1559 0
|
8天前
|
关系型数据库 MySQL 数据库
Python处理数据库:MySQL与SQLite详解 | python小知识
本文详细介绍了如何使用Python操作MySQL和SQLite数据库,包括安装必要的库、连接数据库、执行增删改查等基本操作,适合初学者快速上手。
71 15