Innodbreadonly事務、MySQL5.7和Percona的事務改進
前言
只讀事務在MySQL5.6中引入,改進了建立檢視快照的開銷,減少了持有trx_sys->mutex的時間,這有利於提升只讀效能;這一點已經廣為人知;
本文的內容基本按照讀程式碼的順序來的,先了解了下Oracle MySQL5.6.15的只讀事務部分程式碼,再看了Percona5.6對於事務部分的相關改進;隨後大概過了下Oracle MySQL5.7對事務部分的優化;
總的來說,Percona移植了其在5.5上所做的優化,而Oracle MySQL5.7優化的更徹底,很多程式碼都重構了。
本文不涉及到效能測試,只是程式碼閱讀過程的筆記,記錄的目的是方便以後查閱方便,因此同時也附帶上了一些新版本修改的Rev號。
1.如何使用只讀事務
a.設定變數tx_read_only,當全域性設定為true時,涉及到的SQL只能是隻讀的。這個引數可以是session 級別,也可以是全域性級別;
開啟該引數後,就預設所有查詢走只讀的邏輯;
b.開啟事務時指明:
START TRANSACTION READ ONLY;
c.autocommit狀態下的查詢操作也會被當做只讀事務
如果事務中混合了DML操作,就會報如下錯誤:
root@test 09:57:58>delete from t1;
ERROR 1792 (25006): Cannot execute statement in a READ ONLY transaction.
2.只讀事務涉及的程式碼邏輯(MySQL5.6)
Innodb將所有的事務物件維護在連結串列上,通過trx_sys來管理,在5.6中,最明顯的變化就是事務連結串列被拆分成了兩個連結串列:
一個是隻讀事務連結串列:ro_trx_list,其他非標記為只讀的事務物件放在連結串列rw_trx_list上;
這種分離,使得讀寫事務連結串列足夠小,建立readview 的MVCC快照的速度更快;
a.開始一個事務
入口函式trx_start_low
1)判斷事務是否是隻讀的;
trx->auto_commit = (trx->api_trx && trx->api_auto_commit)
|| thd_trx_is_auto_commit(trx->mysql_thd);trx->read_only =(trx->api_trx && !trx->read_write)|| (!trx->ddl && thd_trx_is_read_only(trx->mysql_thd))|| srv_read_only_mode;if (!trx->auto_commit) {++trx->will_lock;} else if (trx->will_lock == 0) {trx->read_only = TRUE;}
這裡will_lock 的定義感覺有點奇怪,在做DML時,這總是一個較大的值,但DML事務完成後,並沒用清0,導致隨後的一個select不被認為是一個autocommit no-lock的read only事務;不知道是否是預期中的,寫了個bug:http://bugs.mysql.com/bug.php?id=71164
trx->no = TRX_ID_MAX //初始值被設定為一個極大值
trx->id = trx_sys_get_new_trx_id(); //事務id為當前最大的事務id(trx_sys->max_trx_id)
2)對於read_only的事務,無需去為其分配回滾段(trx_assign_rseg_low)
3)對於read only的事務,只有不是non-locking autocommit select時,才將trx物件加入到ro_trx_list上;
也就是說,autocommit的只讀查詢無需加入活躍事務連結串列。
對於非只讀事務,加入到rw_trx_list上;
b.建立read view快照
每次顯式start transaction with consistent snapshot(在repeatable read 隔離級別下)或者事務的第一條SELECT,都需要去建立一個rearview
建立read view的目的是了限定該查詢的事務可見性
trx_assign_read_view->read_view_open_now->read_view_open_now_low
從函式read_view_open_now_low 可以看出,在建立read view時,只需要考慮讀寫事務連結串列,這有別於之前版本需要掃描全部事務,因為這是trx_sys->mutex的保護之下,因此可以提升效能。
具體的,首先根據讀寫事務連結串列的長度分配read view及一個事務id陣列,兩者分配在同一塊記憶體 (view = read_view_create_low(n_trx, heap));
然後將當前活躍的(狀態不是TRX_STATE_COMMITTED_IN_MEMORY)讀寫事務id(rw_trx_list)拷貝到view->trx_ids陣列中,id順序為降序
ut_list_map(trx_sys->rw_trx_list, &trx_t::trx_list, CreateView(view));
view->low_limit_no被設定為當前活躍事務中最小的trx->no(在trx_commit->trx_commit_low->trx_write_serialisation_history->trx_serialisation_number_get中被賦值,設為當前最大事務trx_sys->max_trx_id+1)。
在掃描完所有的讀寫事務後,設定up_limit_id為當前活躍讀寫事務的最小事務id;
low_limit_no的意思是該read view在讀多版本時,無需去讀事務號小於這個值的undo日誌;
low_limit_id表示所有事務id大於等於該值的事務所做的修改都不應該被該view看到;
up_limit_id 表示所有小於該值的事務,都能被當前view可見;
然後根據low_limit_no順序降序將其插入到rx_sys->view_list連結串列中(read_view_add(view))
c.判斷事務可見性
通過函式read_view_sees_trx_id來進行判斷,對於活躍的事務,通過二分查詢來判斷
d.事務提交
backtrace: innobase_commit->trx_commit_for_mysql->trx_commit->trx_commit_in_memory
這時候對於不同的事務型別有所區分:
#對於autocommit no-lock的事務型別,直接設定完事務狀態,從trx sys的read view連結串列中移除即可;
#對於正常開啟的事務,先釋放鎖(lock_trx_release_locks()),再分別從只讀事務和讀寫事務連結串列中移除;
對於read only的事務,也需要呼叫lock_trx_release_locks,舉個例子:
START TRANSACTION READ ONLY;
select * from sbtest1 where id = 999 lock in share mode;
3.Percona對建立read view的改進
Percona在5.5.30及5.6.11之後的版本中對readview這部分邏輯做了修改,Percona的官方部落格對此進行了描述;
大體的修改為:
a.在開啟一個事務時,如果是讀寫事務,那麼會為其在一個全域性陣列中保留一個slot(trx_start_low->trx_reserve_descriptor(trx))
trx_sys->descriptors是維護活躍事務id的陣列,新的事務 id會從陣列尾部開始找到位置插入其id值;陣列以事務id升序排列
trx_sys->descr_n_used 表示當前讀寫事務的個數;
b.建立read view的記憶體分配(read_view_create_low)不再是從trx物件的heap中分配,而是使用malloc分配,分配好後cache下來,儲存在trx->prebuilt_view中,下次重用該事務物件(trx_t)時就可以重複使用. read_view成員新增max_trx_ids,用於維持活躍事務id陣列的長度,只有當前事務連結串列大於該值時,才需要重分配,分配的陣列大小為當前活躍讀寫事務數的1.1倍.
c.由於已經將事務id有序的儲存在陣列trx_sys->descriptors中,那麼這裡只需要將這個陣列(除了當前事務id)直接進行memcpy即可;這相比Oracle MySQL5.6的便利連結串列的方式效率更高。
有人可能注意到,在5.6原生邏輯中,遍歷活躍讀寫事務連結串列時,還要找到最小的trx->no,將其複製給view->low_limit_no,在Percona的改進裡增加了一個連結串列trx_sys->trx_serial_list,用於維護那些已經分配了序列號的事務(見函式trx_serialisation_number_get),由於分配的過程是有序的,因此只需要取列表的第一個節點即可;
相關函式:read_view_open_now_low
d.在判斷事務可見性時,直接使用c++的bsearch函式;
4.MySQL5.7的事務系統及相關改進
a.MySQL5.7在這部分的程式碼基本上重構了,大量使用C++的類,對於我這樣習慣了innodb C語言格式的人來說,還真有點覺得彆扭。(Rev:6203)
從其在Rev:6203 commit的日誌來看,包含以下改進:
1. Refactor the MVCC code
2. Reuse read views for AC-NL-RO selects3. Use a pool of read views4. Add MVCC class5. Use a trx_id to trx_t* map6. Keep the active trx_id_ts in a vector.7. Pre-allocate a small cache of record and table locks8. Avoid extra work when a transaction is tagged as read-only (during commit).9. General code cleanup
大概掃了下:
#只讀事務不考慮innodb的commit concurrency,提交時不呼叫trx_commit_complete_for_mysql, 不考慮auot-inc 鎖, 無需去喚醒master執行緒,等等等;
#所有MVCC操作使用一個新類MVCC來進行重構;
#系統初始化時,會預先建立1024個read view(trx_sys_create);
所有cache的read view被放到MVCC::m_free連結串列中;
另外一個連結串列是m_views, 用於儲存所有活躍或者標記為關閉的readview,當前只有auto commit no-lock read only的SQL使用這一優化,重用上一個事務的read view;如果只讀期間,沒有任何的分配事務id,也就是沒有寫操作(trx_sys->max_trx_id未發生變化),那麼這個read view會被直接接著使用;(函式MVCC::view_open)
#建立readview的程式碼路徑和之前不同,但入口皆為trx_assign_read_view,呼叫trx_sys->mvcc->view_open(trx->read_view, trx)
直接從m_free連結串列中使用一個空閒的readview,無需分配記憶體(MVCC::get_view)
#拷貝活躍事務id的行為(ReadView::prepare)和Percona版本的類似,都是新加了一個list,trx_sys->serialisation_list來維護進入commit階段分配了序列號的事務(trx->no),直接使用記憶體拷貝,因為在建立讀寫事務時,已經在trx_sys->rw_trx_ids中維護了事務id。
#檢查事務可見性(view->changes_visible(trx_id))
#除了事務read view外,還為鎖系統也分配了記憶體(rec_pool,table_pool)
b.無需顯式的開啟一個只讀事務,自動識別(Rev:5209)
#預設情況下,所有的事務都認為以只讀的方式開啟(除非事務被顯式標示為讀寫操作)
#當遇到寫操作,或者需要加IX/X鎖時,轉換為讀寫模式(見函式trx_start_if_not_started_xa_low);
#只讀事務不分配事務id(trx_start_low);但對於只讀查詢但建立了臨時表的場景,將其設定為讀寫事務
#實際上已經沒有讀事務佇列了(Rev:6788);
c.同樣的事務物件trx_t也為其預分配了記憶體,(Rev:5744),預設為4M位元組的連續記憶體;
在5.7裡增加了一套標準類來處理類似的需要pool的場景
相關文章
- 編輯 Java 中的事務 — JDBC 事務和 JTA 事務JavaJDBC
- 本地事務和分散式事務的區別分散式
- 事務的本質和死鎖的原理・改
- Django 1.6.0 正式釋出,大幅改進事務處理Django
- 十、Redis事務、事務鎖Redis
- sqlite的事務和鎖SQLite
- Oracle的事務和鎖Oracle
- MySQL 的索引和事務MySql索引
- MySQL 事務和鎖MySql
- MySQL事務和鎖MySql
- 分散式事務之Spring事務與JMS事務(二)分散式Spring
- MySQL事務(一)認識事務MySql
- 什麼是事務、事務特性、事務隔離級別、spring事務傳播特性?Spring
- 事務
- MySQL中的事務和MVCCMySqlMVC
- HIBERNATE的SESSION和事務Session
- 分散式事務(一)—分散式事務的概念分散式
- SQL Server 事務及回滾事務SQLServer
- Spring事務配置的五種方式和spring裡面事務的傳播屬性和事務隔離級別Spring
- Spring事務專題(三)事務的基本概念,Mysql事務處理原理SpringMySql
- Redis的事務Redis
- 事務的使用
- 事務物件和命令物件物件
- mysql事務和鎖InnoDBMySql
- Spring的事務管理(二)宣告式事務管理Spring
- 資料庫事務以及事務的四個特性資料庫
- 分散式事務之資料庫事務與JDBC事務實現(一)分散式資料庫JDBC
- 宣告式事務能否和程式設計式事務巢狀使用?程式設計巢狀
- 探究MySQL的DML提交事務的意義和DQL是否有必要提交事務MySql
- SQL事務SQL
- PostgreSQL:事務SQL
- Redis 事務Redis
- redis事務Redis
- oracle 事務Oracle
- mysql 事務MySql
- oracle事務Oracle
- 05事務
- mysql事務MySql