MySQL 5.7: Innodb 事務子系統優化
之前寫了篇博客介紹了Percona Server對Read View的優化,順帶簡單提到了MySQL5.7的事務子系統優化,詳細見http://mysqllover.com/?p=834 。 另外一篇博客http://mysqllover.com/?p=1087 也有所涉及。
本文總體介紹了幾個和事務子系統相關的worklog以及其代碼實現。這部分代碼值得細讀,因為他們是5.7 Innodb比較核心的改動,極大的提升了只讀場景下的性能。
WL#6047
這個worklog包含幾點變化:
第一,無需顯示的開啟只讀事務,所有的事務開始默認為只讀事務,當遇到讀寫SQL時,自動加入讀寫列表。
第二,只讀事務不為其分配事務ID,因此如果SHOW ENGINE INNODB STATUS時看到大量事務的ID為0時,不要覺得奇怪。
該改進帶來的最大的好處是你無需修改你的業務SQL。其實這才是用戶能接受的特性,如果沒有量級別的提升,誰會愿意去改代碼呢?
我們以一個典型的例子來開啟這個話題,首先準備一個簡單的表。隔離級別為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
</blockquote>新分配的事務句柄會加入到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
</blockquote>分配的read view會拷貝當前的活躍事務ID,設置最高和最低可見事務ID,然后加入到活躍事務的read view鏈表上(MVCC::m_views)
c) UPDATE t1 SET b=b+1 WHERE a=2;
row_search_mvcc
lock_table
trx_set_rw_mode
</blockquote>將事務轉換成讀寫事務模式,分配回滾段,分配事務ID。加入讀寫事務鏈表(trx_sys->rw_trx_ids, trx_sys->rw_trx_set, trx_sys->rw_trx_list)。
d) COMMIT; 事務提交
代碼:
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5209
WL#6578
在該worklog種優化了read view的創建,對MVCC控制視圖部分的代碼進行了重構。
具體包括以下幾個方面:
在之前版本中,read view的創建的復雜度為O(N),因為需要掃描讀寫事務鏈表;
現在創建一個read view 需要以下幾步:
Step 1:
view->prepare(trx->id);
拷貝事務ID(不包含自己的事務ID),相當于做一個當前活躍讀寫事務的快照存放在視圖中,直接使用memcpy的方式 (copy_trx_ids(trx_sys->rw_trx_ids)),這一點和percona的優化是一樣的。
設置m_low_limit_no ,m_low_limit_id
Step 2:
view->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
主要更改:
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6203
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6204
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6205
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6224
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6236
在完成上述修改后,無需再維持ro_trx_list了,因為所有事務默認都被當做只讀事務,這個鏈表開銷完全可以忽略掉的。
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/6788
WL#6899
該worklog主要實現了隱式鎖向現式鎖轉換的一個優化點。在獲取活躍事務對象時,無需持有lock_sys mutex鎖。
更詳細的,參考我之前寫的這篇博客:http://mysqllover.com/?p=1035
WL#6906
在連續內存中預分配事務對象,保持內存的連續性有利于編譯器或者CPU做出某些優化,例如內存預取之類的(不是很了解這一塊,不展開敘述)
為了實現事務對象內存分配,回收等,在底層分裝了一些pool類,事務對象實際上被管理在一個池結構中。
后面我再單獨寫一篇博客來介紹新加的這些底層結構。
在啟動時,初始化trx_pools (trx_pool_init),初始化時分配4M內存,在shutdown時釋放(trx_pool_close())
為了管理事務對象池,設計了三個類:TrxFactory,TrxPoolLock,TrxPoolManagerLock
獲取事務對象:trx_create_low —-> trx_pools->get()
釋放事務對象:trx_free —-> trx_pools->free(trx)
http://dev.mysql.com/worklog/task/?id=6906
相關代碼:
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5744
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5750
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5753
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5756
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/5786
原創文章,轉載請注明: 轉載自Simple Life
本文鏈接地址: MySQL 5.7: Innodb 事務子系統優化