Innodb read only事务、MySQL5.7和Percona的事务改进

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介:

前言

只读事务在MySQL5.6中引入,改进了创建视图快照的开销,减少了持有trx_sys->mutex的时间,这有利于提升只读性能;这一点已经广为人知;
 
本文的内容基本按照读代码的顺序来的,先了解了下Oracle MySQL5.6.15的只读事务部分代码,再看了Percona5.6对于事务部分的相关改进;随后大概过了下Oracle MySQL5.7对事务部分的优化;
 
总的来说,Percona移植了其在5.5上所做的优化,而Oracle MySQL5.7优化的更彻底,很多代码都重构了。
 
本文不涉及到性能测试,只是代码阅读过程的笔记,记录的目的是方便以后查阅方便,因此同时也附带上了一些新版本修改的Rev号。
 

1.如何使用只读事务

 
a.设置变量tx_read_only,当全局设置为true时,涉及到的SQL只能是只读的。这个参数可以是session 级别,也可以是全局级别;
开启该参数后,就默认所有查询走只读的逻辑;
 
b.开启事务时指明:
START TRANSACTION READ ONLY;
 
c.autocommit状态下的查询操作也会被当做只读事务
 
如果事务中混合了DML操作,就会报如下错误:

root@test 09:57:58>delete from t1;

ERROR 1792 (25006): Cannot execute statement in a READ ONLY transaction. 

2.只读事务涉及的代码逻辑(MySQL5.6)

 
Innodb将所有的事务对象维护在链表上,通过trx_sys来管理,在5.6中,最明显的变化就是事务链表被拆分成了两个链表:
一个是只读事务链表:ro_trx_list,其他非标记为只读的事务对象放在链表rw_trx_list上;
 
这种分离,使得读写事务链表足够小,创建readview 的MVCC快照的速度更快;
 
a.开始一个事务
入口函数trx_start_low 
1)判断事务是否是只读的;

        trx->auto_commit = (trx->api_trx && trx->api_auto_commit)

                           || thd_trx_is_auto_commit(trx->mysql_thd);
 
        trx->read_only =
                (trx->api_trx && !trx->read_write)
                || (!trx->ddl && thd_trx_is_read_only(trx->mysql_thd))
                || srv_read_only_mode;
 
        if (!trx->auto_commit) {
                ++trx->will_lock;
        } else if (trx->will_lock == 0) {
                trx->read_only = TRUE;
        }
 
这里will_lock 的定义感觉有点奇怪,在做DML时,这总是一个较大的值,但DML事务完成后,并没用清0,导致随后的一个select不被认为是一个autocommit no-lock的read only事务;不知道是否是预期中的,写了个bug:http://bugs.mysql.com/bug.php?id=71164 
 
trx->no = TRX_ID_MAX      //初始值被设置为一个极大值
trx->id = trx_sys_get_new_trx_id(); //事务id为当前最大的事务id(trx_sys->max_trx_id)  
 
2)对于read_only的事务,无需去为其分配回滚段(trx_assign_rseg_low)
 
3)对于read only的事务,只有不是non-locking autocommit select时,才将trx对象加入到ro_trx_list上;
也就是说,autocommit的只读查询无需加入活跃事务链表。
 
对于非只读事务,加入到rw_trx_list上;
 
b.创建read view快照
每次显式start transaction with consistent snapshot(在repeatable read 隔离级别下)或者事务的第一条SELECT,都需要去创建一个rearview
创建read view的目的是了限定该查询的事务可见性
 
trx_assign_read_view->read_view_open_now->read_view_open_now_low 
 
从函数read_view_open_now_low 可以看出,在创建read view时,只需要考虑读写事务链表,这有别于之前版本需要扫描全部事务,因为这是trx_sys->mutex的保护之下,因此可以提升性能。
 
具体的,首先根据读写事务链表的长度分配read view及一个事务id数组,两者分配在同一块内存 (view = read_view_create_low(n_trx, heap));
 
然后将当前活跃的(状态不是TRX_STATE_COMMITTED_IN_MEMORY)读写事务id(rw_trx_list)拷贝到view->trx_ids数组中,id顺序为降序
ut_list_map(trx_sys->rw_trx_list, &trx_t::trx_list, CreateView(view)); 
view->low_limit_no被设置为当前活跃事务中最小的trx->no(在trx_commit->trx_commit_low->trx_write_serialisation_history->trx_serialisation_number_get中被赋值,设为当前最大事务trx_sys->max_trx_id+1)。
 
在扫描完所有的读写事务后,设置up_limit_id为当前活跃读写事务的最小事务id;
 
low_limit_no的意思是该read view在读多版本时,无需去读事务号小于这个值的undo日志;
low_limit_id表示所有事务id大于等于该值的事务所做的修改都不应该被该view看到;
up_limit_id 表示所有小于该值的事务,都能被当前view可见;
 
然后根据low_limit_no顺序降序将其插入到rx_sys->view_list链表中(read_view_add(view))
 
c.判断事务可见性
通过函数read_view_sees_trx_id来进行判断,对于活跃的事务,通过二分查找来判断
  
d.事务提交
backtrace: innobase_commit->trx_commit_for_mysql->trx_commit->trx_commit_in_memory
 
这时候对于不同的事务类型有所区分:
#对于autocommit no-lock的事务类型,直接设置完事务状态,从trx sys的read view链表中移除即可;
#对于正常开启的事务,先释放锁(lock_trx_release_locks()),再分别从只读事务和读写事务链表中移除;
对于read only的事务,也需要调用lock_trx_release_locks,举个例子:
    START TRANSACTION READ ONLY;
    select * from sbtest1 where id = 999 lock in share mode; 

3.Percona对创建read view的改进

 
Percona在5.5.30及5.6.11之后的版本中对readview这部分逻辑做了修改,Percona的官方博客对此进行了描述;
 
大体的修改为:
a.在开启一个事务时,如果是读写事务,那么会为其在一个全局数组中保留一个slot(trx_start_low->trx_reserve_descriptor(trx))
trx_sys->descriptors是维护活跃事务id的数组,新的事务 id会从数组尾部开始找到位置插入其id值;数组以事务id升序排列
trx_sys->descr_n_used 表示当前读写事务的个数;
 
b.创建read view的内存分配(read_view_create_low)不再是从trx对象的heap中分配,而是使用malloc分配,分配好后cache下来,存储在trx->prebuilt_view中,下次重用该事务对象(trx_t)时就可以重复使用. read_view成员新增max_trx_ids,用于维持活跃事务id数组的长度,只有当前事务链表大于该值时,才需要重分配,分配的数组大小为当前活跃读写事务数的1.1倍.
 
c.由于已经将事务id有序的存储在数组trx_sys->descriptors中,那么这里只需要将这个数组(除了当前事务id)直接进行memcpy即可;这相比Oracle MySQL5.6的便利链表的方式效率更高。
 
有人可能注意到,在5.6原生逻辑中,遍历活跃读写事务链表时,还要找到最小的trx->no,将其复制给view->low_limit_no,在Percona的改进里增加了一个链表trx_sys->trx_serial_list,用于维护那些已经分配了序列号的事务(见函数trx_serialisation_number_get),由于分配的过程是有序的,因此只需要取列表的第一个节点即可;
 
相关函数:read_view_open_now_low
 
d.在判断事务可见性时,直接使用c++的bsearch函数;

4.MySQL5.7的事务系统及相关改进

 
a.MySQL5.7在这部分的代码基本上重构了,大量使用C++的类,对于我这样习惯了innodb C语言格式的人来说,还真有点觉得别扭。(Rev:6203)
从其在Rev:6203 commit的日志来看,包含以下改进:

 1. Refactor the MVCC code

 2. Reuse read views for AC-NL-RO selects
 3. Use a pool of read views
 4. Add MVCC class
 5. Use a trx_id to trx_t* map
 6. Keep the active trx_id_ts in a vector.
 7. Pre-allocate a small cache of record and table locks
 8. Avoid extra work when a transaction is tagged as read-only (during commit).
 9. General code cleanup
 
大概扫了下:
#只读事务不考虑innodb的commit concurrency,提交时不调用trx_commit_complete_for_mysql, 不考虑auot-inc 锁, 无需去唤醒master线程,等等等;
#所有MVCC操作使用一个新类MVCC来进行重构;
#系统初始化时,会预先创建1024个read view(trx_sys_create);
所有cache的read view被放到MVCC::m_free链表中;
另外一个链表是m_views, 用于存储所有活跃或者标记为关闭的readview,当前只有auto commit no-lock read only的SQL使用这一优化,重用上一个事务的read view;如果只读期间,没有任何的分配事务id,也就是没有写操作(trx_sys->max_trx_id未发生变化),那么这个read view会被直接接着使用;(函数MVCC::view_open)
#创建readview的代码路径和之前不同,但入口皆为trx_assign_read_view,调用trx_sys->mvcc->view_open(trx->read_view, trx)
直接从m_free链表中使用一个空闲的readview,无需分配内存(MVCC::get_view)
 
#拷贝活跃事务id的行为(ReadView::prepare)和Percona版本的类似,都是新加了一个list,trx_sys->serialisation_list来维护进入commit阶段分配了序列号的事务(trx->no),直接使用内存拷贝,因为在创建读写事务时,已经在trx_sys->rw_trx_ids中维护了事务id。
 
#检查事务可见性(view->changes_visible(trx_id))
 
#除了事务read view外,还为锁系统也分配了内存(rec_pool,table_pool)
 
 
b.无需显式的开启一个只读事务,自动识别(Rev:5209)
#默认情况下,所有的事务都认为以只读的方式开启(除非事务被显式标示为读写操作)
#当遇到写操作,或者需要加IX/X锁时,转换为读写模式(见函数trx_start_if_not_started_xa_low);
#只读事务不分配事务id(trx_start_low);但对于只读查询但创建了临时表的场景,将其设置为读写事务
#实际上已经没有读事务队列了(Rev:6788);
 
 
c.同样的事务对象trx_t也为其预分配了内存,(Rev:5744),默认为4M字节的连续内存;
在5.7里增加了一套标准类来处理类似的需要pool的场景
 

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
17天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
125 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
2月前
|
存储 SQL 关系型数据库
MySQL的事务隔离级别
【10月更文挑战第17天】MySQL的事务隔离级别
101 43
|
17天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的表空间
InnoDB是MySQL默认的存储引擎,主要由存储结构、内存结构和线程结构组成。其存储结构分为逻辑和物理两部分,逻辑存储结构包括表空间、段、区和页。表空间是InnoDB逻辑结构的最高层,所有数据都存放在其中。默认情况下,InnoDB有一个共享表空间ibdata1,用于存放撤销信息、系统事务信息等。启用参数`innodb_file_per_table`后,每张表的数据可以单独存放在一个表空间内,但撤销信息等仍存放在共享表空间中。
|
17天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的段、区和页
MySQL的InnoDB存储引擎逻辑存储结构与Oracle相似,包括表空间、段、区和页。表空间由段和页组成,段包括数据段、索引段等。区是1MB的连续空间,页是16KB的最小物理存储单位。InnoDB是面向行的存储引擎,每个页最多可存放7992行记录。
|
17天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL的InnoDB存储引擎
InnoDB是MySQL的默认存储引擎,广泛应用于互联网公司。它支持事务、行级锁、外键和高效处理大量数据。InnoDB的主要特性包括解决不可重复读和幻读问题、高并发度、B+树索引等。其存储结构分为逻辑和物理两部分,内存结构类似Oracle的SGA和PGA,线程结构包括主线程、I/O线程和其他辅助线程。
【赵渝强老师】MySQL的InnoDB存储引擎
|
2月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1648 14
|
3月前
|
存储 Oracle 关系型数据库
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
从基本特性、技术选型、字段类型、事务提交方式、SQL语句、分页方法等方面对比Oracle和MySQL的区别。
555 18
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
|
2月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
3月前
|
SQL 关系型数据库 MySQL
MySQL基础:事务
本文详细介绍了数据库事务的概念及操作,包括事务的定义、开启、提交与回滚。事务作为一组不可分割的操作集合,确保了数据的一致性和完整性。文章还探讨了事务的四大特性(原子性、一致性、隔离性、持久性),并分析了并发事务可能引发的问题及其解决方案,如脏读、不可重复读和幻读。最后,详细讲解了不同事务隔离级别的特点和应用场景。
149 4
MySQL基础:事务
|
2月前
|
SQL 关系型数据库 MySQL
【MySQL】索引和事务
【MySQL】索引和事务
55 0