额,这个标题有点大,实际上只是我在测试5.7性能过程中遇到的一个问题的解惑.不包含5.7的全部read view优化
———–
最近在测试MySQL5.7的只读性能时,和5.6版本对比,发现一个有趣的现象,即在我们的内部版本5.6里,trx_sys->mutex排名第一,而5.7版本则几乎完全看不到该mutex,测试的负载也比较简单,sysbench,使用auto-commit的pk查询
先来看看performance schema的输出:
MySQL5.6.16( heavily patched)
root@performance_schema 11:45:34>SELECT COUNT_STAR, SUM_TIMER_WAIT, AVG_TIMER_WAIT, EVENT_NAME FROM events_waits_summary_global_by_event_name where COUNT_STAR > 0 and EVENT_NAME like ‘wait/synch/%’ order by SUM_TIMER_WAIT desc limit 20;
+————+——————+—————-+—————————————————+
| COUNT_STAR | SUM_TIMER_WAIT | AVG_TIMER_WAIT | EVENT_NAME |
+————+——————+—————-+—————————————————+
| 67106658 | 2191100230566684 | 32650732 | wait/synch/mutex/innodb/trx_sys_mutex |
| 33431350 | 116859287127016 | 3495412 | wait/synch/rwlock/sql/LOCK_grant |
| 67160439 | 89180859682708 | 1327620 | wait/synch/rwlock/sql/MDL_lock::rwlock |
| 63988674 | 67704502627896 | 1057736 | wait/synch/mutex/sql/MDL_map::mutex |
| 67087915 | 47565299538868 | 708936 | wait/synch/mutex/sql/LOCK_table_cache |
| 253945845 | 24152737750000 | 95048 | wait/synch/mutex/sql/THD::LOCK_thd_data |
| 67076196 | 21521813780432 | 320460 | wait/synch/mutex/mysys/THR_LOCK::mutex |
| 101588861 | 19607984308264 | 192712 | wait/synch/rwlock/innodb/hash_table_locks |
| 181625203 | 14086715074644 | 77172 | wait/synch/mutex/innodb/trx_mutex |
| 90361030 | 9755600019708 | 107692 | wait/synch/mutex/innodb/trx_undo_mutex |
| 33554506 | 5639702314236 | 167860 | wait/synch/rwlock/innodb/index_tree_rw_lock |
| 397299 | 97044675080 | 244160 | wait/synch/mutex/innodb/os_mutex |
| 43 | 37606650696 | 874573272 | wait/synch/mutex/sql/LOG::LOCK_log |
| 20152 | 7952989672 | 394580 | wait/synch/mutex/sql/LOCK_open |
| 19316 | 4096687392 | 211896 | wait/synch/mutex/innodb/innobase_share_mutex |
| 28983 | 3267197392 | 112488 | wait/synch/mutex/innodb/dict_sys_mutex |
| 10848 | 1396710304 | 128620 | wait/synch/mutex/innodb/buf_pool_mutex |
| 8791 | 820647920 | 93304 | wait/synch/mutex/sql/LOCK_global_system_variables |
| 4579 | 765776448 | 166988 | wait/synch/mutex/sql/LOCK_plugin |
| 9659 | 522640176 | 54064 | wait/synch/mutex/innodb/file_format_max_mutex |
+————+——————+—————-+—————————————————+
MySQL5.7.5
root@performance_schema 03:01:45>SELECT COUNT_STAR, SUM_TIMER_WAIT, AVG_TIMER_WAIT, EVENT_NAME FROM events_waits_summary_global_by_event_name where COUNT_STAR > 0 and EVENT_NAME like ‘wait/synch/%’ order by SUM_TIMER_WAIT desc limit 20;
+————+—————-+—————-+—————————————————+
| COUNT_STAR | SUM_TIMER_WAIT | AVG_TIMER_WAIT | EVENT_NAME |
+————+—————-+—————-+—————————————————+
| 6205753 | 5513113478224 | 888132 | wait/synch/rwlock/sql/LOCK_grant |
| 12428382 | 4463207301384 | 358828 | wait/synch/mutex/sql/LOCK_table_cache |
| 34298703 | 4162953914096 | 121208 | wait/synch/mutex/sql/THD::LOCK_query_plan |
| 18881544 | 4085456883944 | 216256 | wait/synch/sxlock/innodb/hash_table_locks |
| 20546361 | 2594608664108 | 126004 | wait/synch/mutex/sql/THD::LOCK_thd_data |
| 6232362 | 1653423256736 | 265088 | wait/synch/sxlock/innodb/index_tree_rw_lock |
| 13699272 | 1590037239708 | 115976 | wait/synch/mutex/sql/THD::LOCK_thd_query |
| 29688 | 2876063536 | 96792 | wait/synch/mutex/innodb/fil_system_mutex |
| 16923 | 2151605936 | 126876 | wait/synch/mutex/innodb/buf_pool_mutex |
| 5214 | 559759472 | 107256 | wait/synch/mutex/innodb/log_sys_mutex |
| 1664 | 355917264 | 213640 | wait/synch/mutex/innodb/innobase_share_mutex |
| 2614 | 291241024 | 111180 | wait/synch/mutex/innodb/dict_sys_mutex |
| 1683 | 251709776 | 149548 | wait/synch/mutex/sql/LOCK_open |
| 945 | 148695184 | 156960 | wait/synch/mutex/innodb/file_format_max_mutex |
| 1683 | 132639920 | 78480 | wait/synch/mutex/sql/LOCK_global_system_variables |
| 896 | 83248096 | 92868 | wait/synch/mutex/innodb/flush_list_mutex |
| 234 | 69119952 | 295172 | wait/synch/mutex/sql/LOCK_connection_count |
| 598 | 58556544 | 97664 | wait/synch/mutex/sql/LOCK_plugin |
| 117 | 46728736 | 399376 | wait/synch/mutex/sql/LOCK_transaction_cache |
| 286 | 44845216 | 156524 | wait/synch/mutex/sql/LOCK_thd_list |
+————+—————-+—————-+—————————————————+
20 rows in set (0.54 sec)
很奇怪,在5.7里几乎完全看不到trx_sys mutex,而在我们优化版本里,却非常的明显,直接排在第一位了。
简单看了下代码,在5.6里read_view_remove 需要持有try sys mutex锁。而在5.7里,对应MVCC::view_close 对于只读事务无需持有trx_sys->mutex
另外一个原因是,在分配read view时,5.7是有针对性的对纯读场景下的视图做了优化,可以直接重用。
也就是说,对于只读场景,无论是read view的分配还是释放,都无需获取trx_sys->mutex.
以下简单的介绍下相关逻辑。
0.新的结构体MVCC,用来管理所有的read view
MVCC类的对象挂在trx_sys->mvcc上,在初始化事务子系统时创建
trx_sys->mvcc = UT_NEW_NOKEY(MVCC(1024));
(5.7里使用统一的接口来分配<UT_NEW, UT_NEW_NOKEY> 和释放内存<UT_DELETE> ,主要是为了能监控内存的使用状况
1024表示默认默认初始缓存的readview个数
)
MVCC:
在5.7里采用缓存rearview的方式避免过多的分配/释放readview内存,因此创建了两个链表:
m_free : 表示当前空闲的read view
m_views:表示当前活跃的read view
初始化时所有的read view 都加入到空闲链表上。
1.赋予readview
row_search_mvcc->trx_assign_read_view->MVCC::view_open
两种情况,一种是重用已经为当前事务分配了readview (事务结束后依然属于该事务对象,而不是直接回收),另外一种情况是从空闲链表上新分配read view.
对于前者,如果当前会话只读事务 并且没有活跃读写事务,那么该read view可以被重用。如果当前存在读写事务,则需要将该read view从m_views移除,走正常分配流程.
在分配readview对象时,如果m_free上没有空闲的,那么就重新malloc一个新的rearview对象.
对于重用的场景,理论上应该可以做更近一步的优化.这里重用的逻辑还是太简单粗暴了,必须在只读场景下才能直接重用readview.
2.关闭readview
trx_commit_in_memory->MVCC::view_close
这里也分两种情况:
第一种情况,在auto-commit-read-only场景下,无需持有trx_sys mutex,直接将read view设置为关闭
ptr->m_closed = true;
而对于读写事务,则需要从MVCC::m_views链表中移除并加入到MVCC::m_free链表上。这需要持有trx_sys->mutex
另外一个关闭readview的trace:
in ha_innobase::external_lock->MVCC::view_close, 也就是read-commit级别,如果显式的开启事务,不管是不是只读的,都需要关闭事务,并在下一条SQL再次打开read view.这种情况下的read view是不可重用的。
3.purge线程
purge线程总是需要根据最老的视图clone一个readview,以确定哪些数据可以被purge掉。
trx_sys->mvcc->clone_oldest_view(&purge_sys->view)
这里会逆序遍历m_views,找到一个状态不是被closed的read view,然后继续。
4.问题
在关闭read view时,总是要对read view指针 (p & ~1),也就是最低位置0,如果需要重用read view时,就将read view 低位置1 (reinterpret_cast<ReadView*>(p | 0x1))
而在重用read view时,又将read view的低位恢复为0,这种修改指针的方式看起来是安全的,因为内存总是在偶数位上开始分配
不过没想通这么干的含义,除了标示这个read view被close了并且可能被事务重用外,没看到别的用途.
后面再仔细看看.
5.TODO
根据上述,我们可以针对性的改造5.6的MVCC子系统。
当前我们的内部分支已经合并了percona对事务系统的优化, readview对象被缓存到prebuilt_view中。
更具体的,参考官方worklog:
http://dev.mysql.com/worklog/task/?id=6578