InnoDB事務(wù)隔離級(jí)別實(shí)現(xiàn)原理

數(shù)據(jù)庫(kù)一般都會(huì)并發(fā)執(zhí)行多個(gè)事務(wù),多個(gè)事務(wù)可能會(huì)并發(fā)的對(duì)相同的一批數(shù)據(jù)進(jìn)行增刪改查操作,可能就會(huì)導(dǎo)致我們說的臟寫、臟讀、不可重復(fù)讀、幻讀這些問題。

這些問題的本質(zhì)都是數(shù)據(jù)庫(kù)的多事務(wù)并發(fā)問題,為了解決多事務(wù)并發(fā)問題,數(shù)據(jù)庫(kù)設(shè)計(jì)了事務(wù)隔離機(jī)制、鎖機(jī)制、MVCC多版本并發(fā)控制隔離機(jī)制,用一整套機(jī)制來解決多事務(wù)并發(fā)問題。

1.并發(fā)事務(wù)帶來的問題以及事務(wù)隔離級(jí)別

并發(fā)事務(wù)處理帶來的問題:

  • 更新丟失(Lost Update)或臟寫
      當(dāng)兩個(gè)或多個(gè)事務(wù)選擇同一行,然后基于最初選定的值更新該行時(shí),由于每個(gè)事務(wù)都不知道其他事務(wù)的存在,就會(huì)發(fā)生丟失更新問題–最后的更新覆蓋了由其他事務(wù)所做的更新。
  • 臟讀(Dirty Reads)
      一個(gè)事務(wù)正在對(duì)一條記錄做修改,在這個(gè)事務(wù)完成并提交前,這條記錄的數(shù)據(jù)就處于不一致的狀態(tài);這時(shí),另一個(gè)事務(wù)也來讀取同一條記錄,如果不加控制,第二個(gè)事務(wù)讀取了這些“臟”數(shù)據(jù),并據(jù)此作進(jìn)一步的處理,就會(huì)產(chǎn)生未提交的數(shù)據(jù)依賴關(guān)系。這種現(xiàn)象被形象的叫做“臟讀”。
      一句話:事務(wù)A讀取到了事務(wù)B已經(jīng)修改但尚未提交的數(shù)據(jù),還在這個(gè)數(shù)據(jù)基礎(chǔ)上做了操作。此時(shí),如果B事務(wù)回滾,A讀取的數(shù)據(jù)無效,不符合一致性要求。
  • 不可重讀(Non-Repeatable Reads)
      一個(gè)事務(wù)在讀取某些數(shù)據(jù)后的某個(gè)時(shí)間,再次讀取以前讀過的數(shù)據(jù),卻發(fā)現(xiàn)其讀出的數(shù)據(jù)已經(jīng)發(fā)生了改變、或某些記錄已經(jīng)被刪除了!這種現(xiàn)象就叫做“不可重復(fù)讀”。
      一句話:事務(wù)A內(nèi)部的相同查詢語句在不同時(shí)刻讀出的結(jié)果不一致,不符合隔離性
  • 幻讀(Phantom Reads)
      一個(gè)事務(wù)按相同的查詢條件重新讀取以前檢索過的數(shù)據(jù),卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù),這種現(xiàn)象就稱為“幻讀”。
      一句話:事務(wù)A讀取到了事務(wù)B提交的新增數(shù)據(jù),不符合隔離性

上述“臟讀”、“不可重復(fù)讀”和“幻讀”可以通過不同的事務(wù)隔離級(jí)別來解決:


2.RC RR隔離級(jí)別是怎樣實(shí)現(xiàn)的?

在可重復(fù)讀隔離級(jí)別Repeatable read,當(dāng)事務(wù)開啟,執(zhí)行任何查詢sql時(shí)會(huì)生成當(dāng)前事務(wù)的一致性視圖read-view,該視圖在事務(wù)結(jié)束之前都不會(huì)變化,這個(gè)視圖由執(zhí)行查詢時(shí)所有未提交事務(wù)id數(shù)組(數(shù)組里最小的id為min_id)和已創(chuàng)建的最大事務(wù)id(max_id)組成,事務(wù)里的任何sql查詢結(jié)果需要從對(duì)應(yīng)版本鏈里的最新數(shù)據(jù)開始逐條跟read-view做比對(duì)從而得到最終的快照結(jié)果。

如果是讀已提交隔離級(jí)別Read committed在每次執(zhí)行查詢sql時(shí)都會(huì)重新生成。

2.1 MVCC

Mysql在可重復(fù)讀隔離級(jí)別下如何保證事務(wù)較高的隔離性,我們上節(jié)課給大家演示過,同樣的sql查詢語句在一個(gè)事務(wù)里多次執(zhí)行查詢結(jié)果相同,就算其它事務(wù)對(duì)數(shù)據(jù)有修改也不會(huì)影響當(dāng)前事務(wù)sql語句的查詢結(jié)果。

這個(gè)隔離性就是靠MVCC(Multi-Version Concurrency Control)機(jī)制來保證的,對(duì)一行數(shù)據(jù)的讀和寫兩個(gè)操作默認(rèn)是不會(huì)通過加鎖互斥來保證隔離性,避免了頻繁加鎖互斥,而在串行化隔離級(jí)別為了保證較高的隔離性是通過將所有操作加鎖互斥來實(shí)現(xiàn)的。

undo日志版本鏈?zhǔn)侵敢恍袛?shù)據(jù)被多個(gè)事務(wù)依次修改過后,在每個(gè)事務(wù)修改完后,Mysql會(huì)保留修改前的數(shù)據(jù)undo回滾日志,并且用兩個(gè)隱藏字段trx_id和roll_pointer把這些undo日志串聯(lián)起來形成一個(gè)歷史記錄版本鏈。

版本鏈比對(duì)規(guī)則:

  • 1)如果 row 的 trx_id 落在綠色部分( trx_id<min_id ),表示這個(gè)版本是已提交的事務(wù)生成的,這個(gè)數(shù)據(jù)是可見的;
  • 2)如果 row 的 trx_id 落在紅色部分( trx_id>max_id ),表示這個(gè)版本是由將來啟動(dòng)的事務(wù)生成的,是不可見的(若 row 的 trx_id 就是當(dāng)前自己的事務(wù)是可見的);
  • 3)如果 row 的 trx_id 落在黃色部分(min_id <=trx_id<= max_id),那就包括兩種情況
    a. 若 row 的 trx_id 在視圖數(shù)組中,表示這個(gè)版本是由還沒提交的事務(wù)生成的,不可見(若 row 的 trx_id 就是當(dāng)前自己的事務(wù)是可見的);
    b. 若 row 的 trx_id 不在視圖數(shù)組中,表示這個(gè)版本是已經(jīng)提交了的事務(wù)生成的,可見。

3.鎖機(jī)制

InnoDB使用不同的鎖策略(Locking Strategy)以及MVCC機(jī)制來實(shí)現(xiàn)不同的隔離級(jí)別。

  • 讀未提交(Read Uncommitted)
    事務(wù)在讀數(shù)據(jù)的時(shí)候不對(duì)數(shù)據(jù)加鎖。
    事務(wù)在修改數(shù)據(jù)的時(shí)候只對(duì)數(shù)據(jù)增加行級(jí)共享鎖。
  • 讀已提交(Read Committed)
    普通讀是快照讀,這是一種不加鎖的一致性讀,底層使用MVCC實(shí)現(xiàn)。
    加鎖的select, update, delete等語句,除了在外鍵約束檢查(foreign-key constraint checking)以及重復(fù)鍵檢查(duplicate-key checking)時(shí)會(huì)封鎖區(qū)間,其他時(shí)刻都只使用記錄鎖。
  • 可重復(fù)讀(Repeatable Read)
    普通的select使用快照讀。
    加鎖的select(select…in share mode/select…for update),update,delete等語句,它們的鎖,依賴于它們是否在唯一索引上使用了唯一的查詢條件,或者范圍查詢條件:
    1)在唯一索引上使用唯一的查詢條件,會(huì)使用記錄鎖,而不會(huì)封鎖記錄之間的間隔,即不會(huì)使用間隙鎖與臨鍵鎖。
    2)范圍查詢條件為非唯一值時(shí),會(huì)使用臨鍵鎖,鎖住索引記錄之間的范圍,避免范圍間插入記錄,以避免產(chǎn)生幻讀,以及避免不可重復(fù)讀。
  • 串行化(Serializable)
    這種事務(wù)隔離級(jí)別下,所有select語句會(huì)被隱式的轉(zhuǎn)化為select…in share mode。
    這會(huì)導(dǎo)致,如果有未提交的事務(wù)正在修改某些行,所有讀取這些行的操作都會(huì)被阻塞。

3.1 鎖分類

  • 從性能上分為樂觀鎖(用版本對(duì)比來實(shí)現(xiàn))和悲觀鎖
  • 從對(duì)數(shù)據(jù)庫(kù)操作的類型分,分為讀鎖和寫鎖(都屬于悲觀鎖)
    讀鎖(共享鎖,S鎖(Shared)):針對(duì)同一份數(shù)據(jù),多個(gè)讀操作可以同時(shí)進(jìn)行而不會(huì)互相影響
    寫鎖(排它鎖,X鎖(eXclusive)):當(dāng)前寫操作沒有完成前,它會(huì)阻斷其他寫鎖和讀鎖
  • 從對(duì)數(shù)據(jù)操作的粒度分,分為表鎖和行鎖

表鎖:每次操作鎖住整張表。開銷小,加鎖快;不會(huì)出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)度最低;一般用在整表數(shù)據(jù)遷移的場(chǎng)景。

無索引行鎖會(huì)升級(jí)為表鎖(RR級(jí)別會(huì)升級(jí)為表鎖,RC級(jí)別不會(huì)升級(jí)為表鎖)。

3.2 行鎖

每次操作鎖住一行數(shù)據(jù)。開銷大,加鎖慢;會(huì)出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度最高。

間隙鎖,鎖的就是兩個(gè)值之間的空隙。間隙鎖是在可重復(fù)讀隔離級(jí)別下才會(huì)生效。

臨鍵鎖(Next-key Locks)是行鎖與間隙鎖的組合。

3.3 鎖優(yōu)化建議

  • 盡可能讓所有數(shù)據(jù)檢索都通過索引來完成,避免無索引行鎖升級(jí)為表鎖
  • 合理設(shè)計(jì)索引,盡量縮小鎖的范圍
  • 盡可能減少檢索條件范圍,避免間隙鎖
  • 盡量控制事務(wù)大小,減少鎖定資源量和時(shí)間長(zhǎng)度,涉及事務(wù)加鎖的sql盡量放在事務(wù)最后執(zhí)行
  • 盡可能低級(jí)別事務(wù)隔離

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容