天天看点

MySQL 5.7: Innodb 事务子系统优化

mysql5.7 : innodb 事务子系统优化

本文总体介绍了几个和事务子系统相关的worklog以及其代码实现。这部分代码值得细读,因为他们是5.7 innodb比较核心的改动,极大的提升了只读场景下的性能。

这个worklog包含几点变化:

第一,无需显示的开启只读事务,所有的事务开始默认为只读事务,当遇到读写sql时,自动加入读写列表。

第二,只读事务不为其分配事务id,因此如果show engine innodb status时看到大量事务的id表现的很怪异时(非常大的整数值),不要觉得奇怪。

该改进带来的最大的好处是你无需修改你的业务sql。其实这才是用户能接受的特性,如果没有量级别的提升,谁会愿意去改代码呢?

不过显而易见的,这个优化也带了某些 运维的‘退化’,例如你再也无法从show engine innodb status中发现一个活跃的长时间不提交的只读事务(例如:begin;select;select…),你需要去查询innodb_trx表来获得这些信息。

我们以一个典型的例子来开启这个话题,首先准备一个简单的表。隔离级别为read-commit

create table t1 (a int primary key, b int);

insert into t1 values (1,rand()*100),(2,rand()*100);

begin;

以begin显式开启一个事务;

b) select * from t1;

分配一个事务句柄:

ha_innobase::open ha_innobase::info_low update_thd check_trx_exists innobase_trx_allocate trx_allocate_for_mysql

新分配的事务句柄会加入到trx_sys->mysql_trx_list,并重复使用。

开始一个只读事务,开启的事务,不分配事务id, 不分配回滚段

row_search_mvcc->trx_start_if_not_started->trx_start_low

assign read view

row_search_mvcc trx_assign_read_view mvcc::view_open

分配的read view会拷贝当前的活跃事务id,设置最高和最低可见事务id,然后加入到活跃事务的read view链表上(mvcc::m_views)

c) update t1 set b=b+1 where a=2;

lock_table trx_set_rw_mode

将事务转换成读写事务模式,分配回滚段,分配事务id。加入读写事务链表(trx_sys->rw_trx_ids, trx_sys->rw_trx_set, trx_sys->rw_trx_list)。

d) commit; 事务提交

代码:

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5209">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5209</a>

在该worklog种优化了read view的创建,对mvcc控制视图部分的代码进行了重构。

具体包括以下几个方面:

在之前版本中,read view的创建的复杂度为o(n),因为需要扫描读写事务链表;

现在创建一个read view 需要以下几步:

step 1:

                view-&gt;prepare(trx-&gt;id);

拷贝事务id(不包含自己的事务id),相当于做一个当前活跃读写事务的快照存放在视图中,直接使用memcpy的方式 (copy_trx_ids(trx_sys-&gt;rw_trx_ids)),这一点和percona的优化是一样的。

设置m_low_limit_no ,m_low_limit_id

step 2:

                view-&gt;complete();

设置视图的m_up_limit_id,表示所有小于这个值的修改都可见

step 3:

                ut_list_add_first(m_views, view);

将视图加入到活跃视图链表中。

b)  在之前版本中是在持有trx_sys mutex时创建的read view。

为了降低分配/释放read view的开销,维护了两个read view链表,一个用于放当前活跃的视图链表,一个用于放空闲的、可分配的视图链表。

当系统启动时,会初始化一定数量的read view放到空闲链表上。

percona实现了类似的方案,不同的是percona的read view在事务完成后不是放到空闲链表,而是下次继续重用(但从活跃链表移除,不管是否是读写事务)

c) 对于autocommit的只读事务,即时当前没有活跃事务,也可能因为创建read view ,而大量别的线程在释放read view,导致trx_sys mutex冲突。

针对该问题,实际上我已经在博文http://mysqllover.com/?p=1087中描述过了,对于自动提交的查询,在关闭read view时是不从视图链表上移除的,在再次开启事务重用该read view时,如果这期间没有读写事务,都无需重新初始化read view,直接使用即可。 因此如果一台实例上的都是自动提交的只读事务,完全可以避免trx_sys mutex的开销。

d) 需要持有trx_sys mutex来遍历rw_trx_list,以判断更改是否可见,或者根据事务id获取事务对象trx_t

由于已经保存了事务id的快照,因此直接根据二分查找查找有序数组即可,无需遍历读写事务链表

参考函数readview::changes_visible

trx_sys_t新增成员rw_trx_set,用于维护从trx_id到trx_t的映射。这样可以根据事务id快速找到对应的事务对象而无需扫描事务链表。参考函数trx_get_rw_trx_by_id

主要更改:

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6203">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6203</a>

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6204">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6204</a>

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6205">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6205</a>

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6224">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6224</a>

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6236">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6236</a>

在完成上述修改后,无需再维持ro_trx_list了,因为所有事务默认都被当做只读事务,这个链表开销完全可以忽略掉的。

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6788">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6788</a>

该worklog主要实现了隐式锁向现式锁转换的一个优化点。在获取活跃事务对象时,无需持有lock_sys mutex锁。

在连续内存中预分配事务对象,保持内存的连续性有利于编译器或者cpu做出某些优化,例如内存预取之类的(不是很了解这一块,不展开叙述)

为了实现事务对象内存分配,回收等,在底层分装了一些pool类,事务对象实际上被管理在一个池结构中。

后面我再单独写一篇博客来介绍新加的这些底层结构。

在启动时,初始化trx_pools (trx_pool_init),初始化时分配4m内存,在shutdown时释放(trx_pool_close())

为了管理事务对象池,设计了三个类:trxfactory,trxpoollock,trxpoolmanagerlock

获取事务对象:trx_create_low —-&gt; trx_pools-&gt;get()

释放事务对象:trx_free —-&gt;  trx_pools-&gt;free(trx)

<a href="http://dev.mysql.com/worklog/task/?id=6906">http://dev.mysql.com/worklog/task/?id=6906</a>

相关代码:

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5744">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5744</a>

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5750">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5750</a>

http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5753

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5756">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5756</a>

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5786">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5786</a>

继续阅读