目錄
0.事務隔離級別
我們都知道mysql innodb引擎支持事務,爲了兼顧事務併發時之間的隔離性(ACID)和性能,提供了以下4種隔離級別及其對應的問題:
隔離級別 | 問題&優勢 | 問題解釋 |
---|---|---|
讀未提交 |
髒、不可重複、幻讀 性能最高 |
髒讀:事務A讀取事務B改寫但未提交的數據,如果回滾,則讀到無效數據。 不可重複讀:事務A多次讀取同一數據返回的結果不同 (update) 幻讀:事務A讀取幾行記錄後,事務B插入(insert)一些記錄。後來的查詢中,事務A發現有些原來沒有的記錄。 |
讀已提交 |
不可重複、幻讀 性能較高 |
|
可重複讀 REPEATABLE-READ |
幻讀 性能平衡 默認選擇 |
|
串行 |
null 依次執行,不會併發 性能差 |
那這4中隔離級別是如何實現的呢?
很顯然讀未提交,innodb引擎啥也沒幹,所有事務都是讀取數據最新的記錄
而串行化也很好理解,就是不允許併發了,加鎖互斥訪問即可,同一數據同時刻僅能被一個事務修改,也不會有以上問題
中間的兩種讀已提交和可重複讀是用的MVCC多版本併發控制來實現的,在保證併發的數據安全問題,也提升了讀寫線程的活躍性。具體的下面詳解~
1.MVCC
- MVCC (Mutil-Version Concurrency Control,多版本併發控制),多個事務併發進行時,對於每行每個事務有自己的Read View版本,這樣就無需給行加讀鎖,就不會阻塞寫操作,以獲得更大的併發度。
- 多版本,一條記錄存在這多個版本,某時刻最新的版本存儲在數據頁上,歷史版本存儲在undo log回滾段中,每行數據和歷史版本有隱藏列DATA_TRX_ID和DATA_ROLL_PTR和舊數據。
DATA_TRX_ID表示更新這個版本的事務id(版本號);
DATA_ROLL_PTR指向上個版本,構成版本鏈。
- trx_id事務id,每個事務begin開始時獲取的全局唯一的連續遞增的序列號(也稱爲版本號)
- Read View 視圖,每個事務(RR,可重複讀)或每條語句(RC,讀已提交)會在開始時創建的結構體,用於版本可見性控制。包含
creator_trx_id | 當前事務id |
up_limit_id | 活躍的最小事務id |
low_limit_id | 活躍的最大事務id |
trx_ids | 活躍事務列表 |
2.版本控制算法
- time1:開啓事務,掃描事務鏈表trx_sys(當前活躍的事務),選取事務id構建Read View
creator_trx_id | 當前事務id |
up_limit_id | 活躍的最小事務id |
low_limit_id | 活躍的最大事務id |
trx_ids | 活躍事務列表 |
- time 2: select 查詢數據 ,從數據頁的數據行(版本鏈的head版本)開始遍歷,如果該版本可見(比較版本號和ReadView)返回,如果不可見走到下一跳(上個版本)。
- times:可見? 比較版本的DATA_TRX_ID和up_limit_id、low_limit_id,判斷DATA_TRX_ID在不在trx_ids列表中,得出結論當前事務能否看見這個版本。
2.1 select 查詢數據僞代碼
select(row, readView){
node = row //數據頁行記錄
while(node.roll_ptr!=null
&& canSee(node.trx_id, readView)) { // 順着版本鏈找到最新的可見的版本
node = *roll_ptr
}
return node.oldVal // 版本值
}
2.2 可見性算法
如下圖,每個箭頭表示一個事務的開始和提交,按照時間順序從上到下
|
canSee(trx_id, readView){
if(trx_id < readView.up_limit_id) //這些數據在事務創建id的時都已經提交
return true;
if(trx_id >= readView.low_limit_id) //該事務在當前事務開始後開始
return false
if(up_limit_id<trx_id<low_limit_id){
if(隔離級別==可重複讀RR)
return false
if(隔離級別==讀已提交RC) {
if(read_view->trx_ids.contains(trx_id))
return false //修改當前版本的事務還活躍,未提交,不可見
else{
return true // 該事務已提交,RC可讀
}
}
}
}