MySQL 性能优化5.7 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表来查询。这是一个需要注意的点,因为很多人都是通过前者来找到长时间未提交的事务。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/32aa798820a73ae0b8e4bf467d6c5acb.html