MySQL内核月报 2014.12-MySQL· 性能优化·5.7 Innodb事务系统

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

背景知识

为了便于理解下文,我们先简单梳理下Innodb中的事务、视图、多版本的相关背景知识。

在Innodb中,每次开启一个事务时,都会为该session分配一个事务对象。而为了对全局所有的事务进行控制和协调,有一个全局对象trx_sys,对trx_sys相关成员的操作需要trx_sys->mutex锁。


Innodb使用一种称做ReadView(视图)的对象来判断事务的可见性(也就是ACID中的隔离性)。根据可见性原则,某个新开启的事务不应该看到其他未提交的事务。 Innodb在执行一个SELECT或者显式开启START TRANSACTION WITH CONSISTENT SNAPSHOT (后者只应用于REPEATABLE-READ隔离级别) 会创建一个视图对象。对于RR隔离级别,视图的生命周期到事务提交结束,对于RC隔离级别,则每条查询开始时重分配事务。


通常一个视图中包含创建视图的事务ID,以及在创建视图时活跃的事务ID数组。例如,当开启一个视图时,当前事务的事务ID为5, 事务链表上活跃事务ID为{2,5,6,9,12},那么就会把{2,6,9,12}存储到当前的视图中(5是当前事务的ID,不记录到视图中),{2,6,9,12}对应的事务所做的修改对当前事务而言都是不可见的,小于2的事务ID对当前事务都是可见的,大于12的事务ID对当前事务是不可见的。


那么如何判断可见性呢? 对于聚集索引,每次修改记录时,都会在记录中保存当前的事务ID,同时旧版本记录存储在UNDO中;对于二级索引,则在二级索引页中存储了更新当前页的最大事务ID,如果该事务ID大于readview->up_limit_id(对于上例,up_limit_id值为2),那么就需要回聚集索引判断记录可见性;如果小于2, 那么总是可见的,可以直接读取。


Innodb的多版本数据使用UNDO来维护的,例如聚集索引记录(1) =>(2)=>(3),从1更新成2,再更新成3,就会产生两条undo记录。当然这不是本文讨论的重点。后续在单独针对临时表的优化时会谈及undo相关的知识。


Innodb事务系统优化


在MySQL 5.7版本里,针对性的对事务系统做了比较深入的优化,主要解决了下面几个问题。


问题一:视图对象的创建需要trx_sys->mutex锁保护


trx_sys->mutex是事务系统最核心的全局锁对象,持有该锁进行的操作都不应该耗时过长。对于read view对象,完全可以将其缓存下来重复使用。这样就避免了持有锁分配视图内存。

因此在MySQL 5.7版本中,实例启动时就分配1024个视图对象;同时维护两个链表,一个是已使用的视图链表,一个是空闲的视图链表;当需要分配新的视图时,总是从空闲视图链表中分配,如果没有,再新分配一个。

在Percona Server中也做了类似的优化,但与5.7不同的是,其不集中管理所有的视图,而是每个事务对象(trx_t)上都挂载一个预分配的视图对象,在事务对象销毁时释放(事务对象本身对session而言也是重用的)。


问题二:视图对象中保存全局事务ID时,需要扫描事务链表


正如上面描述的,为了判断事务视图的可见性,在打开一个视图时需要拷贝当时活跃的事务ID。在5.5及之前版本需要遍历所有的活跃事务,而在5.6中,将事务链表拆分成了只读事务链表,和读写事务链表,这样我们只需要遍历读写事务链表,拷贝事务ID即可。

在5.7中,事务系统维持了一个全局事务ID数组,每个活跃读写事务的ID都被加入到其中,在事务提交时从其中删除,这样打开视图时只需要使用memcpy 拷贝该数组即可,无需遍历链表。在读写链表较长(高并发下)的场景,该优化可以显著的提升性能。不过就该优化点而言,Percona Serve同样走在了前面,相同的思路实现在Percona Server 5.6中。


问题三: 用户需要显式开启只读事务,才会放入只读事务链表


尽量在5.6中已经将事务链表拆分成了只读事务链表和读写事务链表(AUTOCOMMIT的SELECT不加入任何链表),但用户需要显式的指定事务以只读模式打开(START TRANSACTION READ ONLY)或者设置SESSION变量tx_read_only。

显然这种方式对用户而言是极不友好的,因此在5.7中做了比较彻底的改变,将只读事务链表从其中彻底移除了,取而代之的是,所有事务都以只读模式打开。

例如如下事务序列:

BEGIN;

SELECT; //事务开始,不分配事务ID,不分配回滚段;

UPDATE; //分配事务ID并插入全局事务数组和事务对象集合中,分配回滚段;

COMMIT;


而对于BEGIN;SELECT;SELECT;COMMIT这样的序列,整个事务周期既不分配事务ID,也不分配回滚段。

那么问题来了,既然只读的事务不分配事务ID,那么如何标示事务呢,在5.7中,使用事务对象的地址来进行计算得到一个唯一的事务ID。执行’SHOW ENGINE INNODB STATUS’不再显示活跃的只读事务,只能通过INNODB_TRX表来查询。这是一个需要注意的点,因为很多人都是通过前者来找到长时间未提交的事务。

另外一个比较有意思的小优化是,对于AUTOCOMMIT的只读查询,关闭视图时,并不是立刻从视图链表中移除,而是设置一个简单的close标记;该session下次需要打开该read view时,如果这期间没有任何读写事务,就可以直接重用上次的read view,清楚close标记,这样打开、关闭视图都无需获取trx_sys->mutex。


问题四:隐式锁转换为显式锁的开销


Innodb对于类似INSERT操作,采用的是隐式锁的方式,隐式锁不是锁,只是一种称呼而已,只有在需要的时候,才会转换为显式锁。例如如下:

Session 1: BEING; INSERT INTO t1(pk, val) VALUES (1,2); //不创建锁对象

Session 2: UPDATE t1 SET val=val+1 WHERE pk=1; //创建两个锁对象,一个是为session1创建一个记录锁对象,另外一个是给自己创建一个等待类型的记录锁对象,然后session2加入锁等待队列。


在Session 2中为Session1创建锁对象的过程即是所谓的隐式锁向显式锁转换。 当session2扫描到session 1插入的记录时,发现session 1的事务依然活跃,就会进入转换逻辑。

在5.6版本中,其转换过程如下:

1.持有lock_sys->mutex

2. 持有trx_sys->mutex;

根据事务ID,扫描读写事务链表,找到对应的事务对象;

释放trx_sys->mutex;

3.创建显式锁对象

4.释放lock_sys->mutex


可以看到,在该操作的过程中,全程持有lock_sys->mutex,持有锁的原因是防止事务提交掉。当读写事务链表非常长时(例如高并发写入时),这种开销将是不可接受的。

在5.7版本中,上述逻辑则优化成:

1. 持有trx_sys->mutex

根据事务ID找到对应的事务对象(直接查找trx_sys->rw_trx_set,其保存了trx_id和事务对象的映射关系,因此无需扫描读写事务链表)

增加事务对象引用计数(++trx->n_ref)

释放trx_sys->mutex

2. 持有lock_sys->mutex;

创建显式锁对象;

释放lock_sys->mutex;

3.递减事务对象引用计数

在事务commit,释放记录锁前,会先判断引用记录数是否为0,如果不为0,表示正有其他事务为其转换显式锁,这时候需要等待,直到计数为0,才能进入释放事务记录锁阶段。

总的来说,该优化减少了隐式锁转换时持有LOCK_sys->mutex的时间,从而提升性能。


除了上述提到的几点事务优化外,在5.7版本中还对事务系统部分的代码做了重构,完全用C++重写;引入了一个POOL结构,事务对象和锁对象都可以缓存复用。大家可以阅读几个相关的worklog,以更好的理解上述优化:

http://dev.mysql.com/worklog/task/?id=6047

http://dev.mysql.com/worklog/task/?id=6578

http://dev.mysql.com/worklog/task/?id=6899

http://dev.mysql.com/worklog/task/?id=6906


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
5天前
|
存储 关系型数据库 MySQL
《MySQL 简易速速上手小册》第3章:性能优化策略(2024 最新版)
《MySQL 简易速速上手小册》第3章:性能优化策略(2024 最新版)
28 2
|
21天前
|
存储 监控 关系型数据库
轻松入门Mysql:MySQL性能优化与监控,解锁进销存系统的潜力(23)
轻松入门Mysql:MySQL性能优化与监控,解锁进销存系统的潜力(23)
|
1月前
|
存储 关系型数据库 MySQL
MySQL InnoDB数据存储结构
MySQL InnoDB数据存储结构
|
1月前
|
存储 缓存 关系型数据库
MySQL的varchar水真的太深了——InnoDB记录存储结构
varchar(M) 能存多少个字符,为什么提示最大16383?innodb怎么知道varchar真正有多长?记录为NULL,innodb如何处理?某个列数据占用的字节数非常多怎么办?影响每行实际可用空间的因素有哪些?本篇围绕innodb默认行格式dynamic来说说原理。
834 6
MySQL的varchar水真的太深了——InnoDB记录存储结构
|
2月前
|
存储 缓存 关系型数据库
MySQL - 存储引擎MyISAM和Innodb
MySQL - 存储引擎MyISAM和Innodb
|
13天前
|
存储 关系型数据库 MySQL
MySQL引擎对决:深入解析MyISAM和InnoDB的区别
MySQL引擎对决:深入解析MyISAM和InnoDB的区别
28 0
|
23天前
|
存储 SQL 关系型数据库
MySQL性能优化
MySQL性能优化
13 0
|
15天前
|
SQL 缓存 关系型数据库
mysql性能优化-慢查询分析、优化索引和配置
mysql性能优化-慢查询分析、优化索引和配置
80 1
|
21天前
|
SQL 关系型数据库 MySQL
轻松入门MySQL:深入学习数据库表管理,创建、修改、约束、建议与性能优化(3)
轻松入门MySQL:深入学习数据库表管理,创建、修改、约束、建议与性能优化(3)
|
2月前
|
存储 关系型数据库 MySQL
InnoDB 引擎底层事务的原理
InnoDB 引擎底层事务的原理

相关产品

  • 云数据库 RDS MySQL 版