【MySQL源碼分析】淺談Mysql的鎖

業務研發團隊 唐蘊夢html

什麼是鎖

鎖是計算機協調多個進程或線程併發訪問某一資源的機制。node

Mysql鎖

  • 行鎖
    開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的機率最低,併發度也最高。
  • 頁鎖
    開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度通常。
  • 表鎖
    開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的機率最高,併發度最低。

MySQL的表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨佔寫鎖(Table Write Lock)。mysql

事務

事務是指做爲單個邏輯工做單元執行的一系列操做,要麼徹底地執行,要麼徹底地不執行。sql

事務的四個特性條件

  • 原子性:一組事務,要麼所有成功;要麼撤回。
  • 一致性 :知足模式鎖指定的約束,好比銀行轉帳先後總金額應該不變。事務結束時,全部的內部數據結構(如B樹索引)也都必須是正確的。
  • 隔離性:事務獨立運行。一個事務所作的修改在最終提交以前,對其它事務是不可見的。事務的100%隔離,須要犧牲速度。
  • 持久性:軟、硬件崩潰後,InnoDB數據表驅動會利用日誌文件重構修改,或者經過數據庫備份和恢復來保證。

在默認狀況下,MySQL每執行一條SQL語句,都是一個單獨的事務。數據庫

事務併發的問題

  • 髒讀(Dirty Reads):一個事務正在對一條記錄作修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態;這時,另外一個事務也來讀取同一條記錄,若是不加控制,第二個事務讀取了這些「髒」數據,並據此作進一步的處理,就會產生未提交的數據依賴關係。這種現象被形象地叫作"髒讀"。
  • 不可重複讀(Non-Repeatable Reads):一個事務讀取某些數據,在它結束讀取以前,另外一個事務可能完成了對數據行的更改。當第一個事務試圖再次執行同一個查詢,服務器就會返回不一樣的結果。
  • 幻讀(Phantom Reads):一個事務按相同的查詢條件從新讀取之前檢索過的數據,卻發現其餘事務插入了知足其查詢條件的新數據,這種現象就稱爲「幻讀」。

事務隔離級別

事務隔離級別 髒讀 不可重複讀 幻讀
讀未提交(read-uncommitted)
不可重複讀(read-committed)
可重複讀(repeatable-read)
串行化(serializable)

mysql默認的事務隔離級別爲repeatable-read服務器

  • 讀未提交 :事務能夠讀取到其餘事務未提交的數據,此時若A事務讀取到B事務未提交的修改,後B回滾就會產生髒讀。
  • 不可重複讀:事務只能讀取到其餘事務提交的數據,不會產生髒讀,但若事務B提交在A的兩次查詢間就會產生不可重複讀。
  • 可重複讀:可重複讀的隔離級別下使用了MVCC機制,A事務中讀取的是記錄的快照版本,而非最新版本,B事務的更新是建立了一個新版原本更新,不一樣事務的讀和寫是分離的
  • 串行化:mysql中事務隔離級別爲serializable時會鎖表,所以不會出現幻讀的狀況,這種隔離級別併發性極低。

事務加鎖方式

  • 一次性鎖協議,事務開始時,即一次性申請全部的鎖,以後不會再申請任何鎖,若是其中某個鎖不可用,則整個申請就不成功,事務就不會執行,在事務尾端,一次性釋放全部的鎖。一次性鎖協議不會產生死鎖的問題,但事務的併發度不高。
  • 兩階段鎖協議,整個事務分爲兩個階段,前一個階段爲加鎖,後一個階段爲解鎖。在加鎖階段,事務只能加鎖,也能夠操做數據,但不能解鎖,直到事務釋放第一個鎖,就進入解鎖階段,此過程當中事務只能解鎖,也能夠操做數據,不能再加鎖。兩階段鎖協議使得事務具備較高的併發度,由於解鎖沒必要發生在事務結尾。它的不足是沒有解決死鎖的問題,由於它在加鎖階段沒有順序要求。如兩個事務分別申請了A, B鎖,接着又申請對方的鎖,此時進入死鎖狀態。

Innodb的事務隔離

在MVCC併發控制中,讀操做能夠分紅兩類:快照讀 (snapshot read)與當前讀 (current read)。快照讀,讀取的是記錄的可見版本 (有多是歷史版本),不用加鎖。當前讀,讀取的是記錄的最新版本,而且,當前讀返回的記錄,都會加上鎖,保證其餘事務不會再併發修改這條記錄。數據結構

MySQL/InnoDB定義的4種隔離級別:併發

  • Read Uncommited異步

    能夠讀取未提交記錄。
  • Read Committed (RC)
    當前讀操做RC隔離級別保證對讀取到的記錄加鎖 (記錄鎖),存在幻讀現象。使用MVCC,但讀取數據時讀取自身版本和最新版本,以最新爲主,能夠讀已提交記錄,存在不可重複讀現象。
  • Repeatable Read (RR)
    當前讀操做RR隔離級別保證對讀取到的記錄加鎖 (記錄鎖),同時保證對讀取的範圍加鎖,新的知足查詢條件的記錄不可以插入 (間隙鎖),不存在幻讀現象。使用MVCC保存兩個事物操做的數據互相隔離,不存在不可重複讀現象。
  • Serializable
    從MVCC併發控制退化爲基於鎖的併發控制。不區別快照讀與當前讀,全部的讀操做均爲當前讀,讀加讀鎖 (S鎖),寫加寫鎖 (X鎖)。

Serializable隔離級別下,讀寫衝突,所以併發度急劇降低,在MySQL/InnoDB下不建議使用。分佈式

InnoDB的MVCC多版本併發控制

MVCC是一種多版本併發控制機制。鎖機制能夠控制併發操做,可是其系統開銷較大,而MVCC能夠在大多數狀況下代替行級鎖,使用MVCC,能下降其系統開銷。

MVCC是經過保存數據在某個時間點的快照來實現的。 不一樣存儲引擎的MVCC、不一樣存儲引擎的MVCC實現是不一樣的,典型的有樂觀併發控制和悲觀併發控制。

InnoDB的MVCC,是經過在每行記錄後面保存兩個隱藏的列來實現的,這兩個列,分別保存了這個行的建立時間,一個保存的是行的刪除時間。這裏存儲的並非實際的時間值,而是系統版本號(能夠理解爲事務的ID),沒開始一個新的事務,系統版本號就會自動遞增,事務開始時刻的系統版本號會做爲事務的ID。IN

  • INSERT
    InnoDB爲新插入的每一行保存當前系統版本號做爲版本號.
  • UPDATE
    InnoDB執行UPDATE,其實是新插入了一行記錄,並保存其建立時間爲當前事務的ID,同時保存當前事務ID到要UPDATE的行的刪除時間。
  • DELETE
    InnoDB會爲刪除的每一行保存當前系統的版本號(事務的ID)做爲刪除標識
  • SELECT
    InnoDB會根據如下兩個條件檢查每行記錄,須要同時知足如下兩個條件:

    • InnoDB只會查找版本早於當前事務版本的數據行(也就是行的系統版本號小於或等於事務的系統版本號),這樣能夠確保事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或者修改過的.
    • 行的刪除版本要麼未定義要麼大於當前事務版本號,這能夠確保事務讀取到的行,在事務開始以前未被刪除。
**注意:
在SELECT時,只知足上述兩個條件也是不能達到快照讀的要求的,好比在RR的隔離級別下會有以下狀況

啓動1號事務、啓動2號事務、1號事務更新x行並提交事務(此時x行的修改版本號爲1,刪除版本號爲未定義)、2號事務讀取x行

安裝如上步驟,若是隻知足上述兩個條件的話,顯然2號事務時能夠讀取到1號事務所作的更新(x行修改版本號爲1知足小於2,刪除版本號爲未定義知足事務開始以前未刪除),顯然是不足夠知足快照讀的要求**

clipboard.png

事實上,在讀取到知足上述兩個條件的行時,InnoDB還會進行二次檢查,如上圖所示

活躍事務列表:RC隔離級別下,在語句開始時從全局事務表中獲取活躍(未提交)事務構造Read View,RR隔離級別下,在事務開始時從全局事務表中獲取活躍事務構造Read View

  • 1.取當前行的修改事務ID,和Read View中的事務ID作比較,若小於最小的ID或小於最大ID但不在列表中,轉2步驟,若大於最大ID,轉3步驟
  • 2.知足進入此步驟的條件,便可說明,最後更新當前行的事務,在構造Read View時已經提交,則返回當前行的數據
  • 3.知足進入此步驟的條件,便可說明,最後更新當前行的事務,在構造Read View時還未建立或者還未提交,則取undo log中的記錄的事務ID,從新進入步驟1,重複此操做

至此,經過上述步驟,能夠實現真正的快照讀。

上述策略的結果就是,在讀取數據的時候,InnoDB幾乎不用得到任何鎖,每一個查詢都經過版本檢查,只得到本身須要的數據版本,從而大大提升了系統的併發度。 這種策略的缺點是,每行記錄都須要額外的存儲空間,更多的行檢查工做和一些額外的維護工做。

上述更新前創建undo log,根據各類策略讀取時非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,這個可能與咱們所理解的MVCC有較大的出入,通常咱們認爲MVCC有下面幾個特色:

  • 每行數據都存在一個版本,每次數據更新時都更新該版本
  • 修改時Copy出當前版本隨意修改,各個事務之間無干擾
  • 保存時比較版本號,若是成功(commit),則覆蓋原記錄;失敗則放棄copy(rollback)

就是每行都有版本號,保存時根據版本號決定是否成功,聽起來含有樂觀鎖的味道,而Innodb的實現方式是:

  • 事務以排他鎖的形式修改原始數據
  • 把修改前的數據存放於undo log,經過回滾指針與主數據關聯
  • 修改爲功(commit)啥都不作,失敗則恢復undo log中的數據(rollback)

兩者最本質的區別是,當修改數據時是否要排他鎖定,若是鎖定了還算不算是MVCC?

Innodb的實現真算不上MVCC,由於並無實現核心的多版本共存,undo log中的內容只是串行化的結果,記錄了多個事務的過程,不屬於多版本共存。但理想的MVCC是難以實現的,當事務僅修改一行記錄使用理想的MVCC模式是沒有問題的,能夠經過比較版本號進行回滾;但當事務影響到多行數據時,理想的MVCC據無能爲力了。

好比,若是Transaciton1執行理想的MVCC,修改Row1成功,而修改Row2失敗,此時須要回滾Row1,但由於Row1沒有被鎖定,其數據可能又被Transaction2所修改,若是此時回滾Row1的內容,則會破壞Transaction2的修改結果,致使Transaction2違反ACID。

理想MVCC難以實現的根本緣由在於企圖經過樂觀鎖代替二段提交。修改兩行數據,但爲了保證其一致性,與修改兩個分佈式系統中的數據並沒有區別,而二提交是目前這種場景保證一致性的惟一手段。二段提交的本質是鎖定,樂觀鎖的本質是消除鎖定,兩者矛盾,故理想的MVCC難以真正在實際中被應用,Innodb只是借了MVCC這個名字,提供了讀的非阻塞而已。

Innodb事務鎖

鎖模式

  • 共享鎖(S):又稱讀鎖,若事務T對數據對象A加上S鎖,則事務T能夠讀A但不能修改A,其餘事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這保證了其餘事務能夠讀A,但在T釋放A上的S鎖以前不能對A作任何修改。
  • 排他鎖(X):又稱寫鎖。若事務T對數據對象A加上X鎖,事務T能夠讀A也能夠修改A,其餘事務不能再對A加任何鎖,直到T釋放A上的鎖。這保證了其餘事務在T釋放A上的鎖以前不能再讀取和修改A。

另外,爲了容許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。

  • 意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。
  • 意向排他鎖(IX):事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。

意向鎖僅僅用於表鎖和行鎖的共存使用。若是咱們的操做僅僅涉及行鎖,那麼意向鎖不會對咱們的操做產生任何影響。在任一操做給表A的一行記錄加鎖前,首先要給該表加意向鎖,若是得到了意向鎖,而後纔會加行鎖,並在加行鎖時判斷是否衝突。若是如今有一個操做要得到表A的表鎖,因爲意向鎖的存在,表鎖獲取會失敗(若是沒有意向鎖的存在,加表鎖以前可能要遍歷整個聚簇索引,判斷是否有行鎖存在,若是沒有行鎖才能加表鎖)。   

同理,若是某一操做已經得到了表A的表鎖,那麼另外一操做得到行鎖以前,首先會檢查是否能夠得到意向鎖,並在得到意向鎖失敗後,等待表鎖操做的完成。也就是說:1.意向鎖是表級鎖,可是卻表示事務正在讀或寫某一行記錄;2.意向鎖之間不會衝突, 由於意向鎖僅僅表明要對某行記錄進行操做,在加行鎖時,會判斷是否衝突;3.意向鎖是InnoDB自動加的,不需用戶干預。

鎖類型

  • 間隙鎖(Gap Lock),只鎖間隙。表現爲鎖住一個區間(注意這裏的區間都是開區間,也就是不包括邊界值)。
  • 記錄鎖(Record Lock),只鎖記錄。表現爲僅僅鎖着單獨的一行記錄。
  • Next-Key鎖(源碼中稱爲Ordinary Lock),同時鎖住記錄和間隙。從實現的角度爲record lock+gap lock,並且兩種鎖有可能只成功一個,因此next-key是半開半閉區間,且是下界開,上界閉。一張表中的next-key鎖包括:(負無窮大,最小的第一條記錄],(記錄之間],(最大的一條記錄,正無窮大)。
  • 插入意圖鎖(Insert Intention Lock),插入操做時使用的鎖。在代碼中,插入意圖鎖其實是Gap鎖上加了一個LOCK_INSERT_INTENTION的標記。也就是說insert語句會對插入的行加一個X記錄鎖,可是在插入這個行的過程以前,會設置一個Insert intention的Gap鎖,叫作Insert intention鎖。
  • 樂觀鎖 : 老是認爲不會產生併發問題,每次去取數據的時候總認爲不會有其餘線程對數據進行修改,所以不會上鎖,可是在更新時會判斷其餘線程在這以前有沒有對數據進行修改,通常會使用版本號機制或CAS操做實現。version方式:通常是在數據表中加上一個數據版本號version字段,表示數據被修改的次數,當數據被修改時,version值會加一。當線程A要更新數據值時,在讀取數據的同時也會讀取version值,在提交更新時,若剛纔讀取到的version值爲當前數據庫中的version值相等時才更新,不然重試更新操做,直到更新成功。
  • 悲觀鎖: 老是假設最壞的狀況,每次取數據時都認爲其餘線程會修改,因此都會加鎖(讀鎖、寫鎖、行鎖等),當其餘線程想要訪問數據時,都須要阻塞掛起。能夠依靠數據庫實現,如行鎖、讀鎖和寫鎖等,都是在操做以前加鎖。

一致性非鎖定讀

InnoDB使用MVCC來實現一致性非鎖定讀,在read-committed和repeatable-read兩種事務隔離級別下使用,且效果不一樣,具體以下。

read-committed

在讀已提交的隔離級別下,事務在一致性非鎖定讀始終讀取當前最新的數據快照,即當其餘事務提交更新後快照更新也會讀取最新的,也就是出現不可重複讀。

repeatable-read

在可重複讀的隔離級別下,事務始終讀取事務開始時的快照版本。

一致性鎖定讀

一致性鎖定讀有兩種實現方式,一種是加X鎖,一種是加S鎖

select ... for update 顯示的使用加X鎖的方式讀取

select ... lock in share mode 顯示的使用加S鎖的方式讀取

自增加與鎖

innodb_autoinc_lock_mode有3種配置模式:0、一、2,

  • 0:涉及auto-increment列的插入語句加的表級AUTO-INC鎖,只有插入執行結束後纔會釋放鎖,即事務在進行插入時獲取自增加值時先加鎖,後插入,插入完釋放
  • 1:對於能夠事先肯定插入行數的語句(包括單行和多行插入),使用互斥量操做自增值,分配連續的肯定的auto-increment值,對於插入行數不肯定的插入語句仍使用表級AUTO-INC鎖。這種模式下,事務回滾,auto-increment值不會回滾,換句話說,自增列內容會不連續。
  • 2:對於全部的插入操做使用互斥量操做自增值,來一個插入分配一個auto-increment值,此時一個批量插入的自增加值就可能不連續,且在sql語句級的主從同步可能會出現問題

鎖升級

InnoDB行鎖是經過給索引上的索引項加鎖來實現的,這一點MySQL與Oracle不一樣,後者是經過在數據塊中對相應數據行加鎖來實現的。InnoDB這種行鎖實現特色意味着:只有經過索引條件檢索數據,InnoDB才使用行級鎖,不然,InnoDB將使用表鎖!

InnoDB目前處理死鎖的方法是:將持有最少行級排它鎖的事務回滾。若是是由於死鎖引發的回滾,能夠考慮在應用程序中從新執行。

在事務中,若是要更新記錄,應該直接申請足夠級別的鎖,即排他鎖,而不該先申請共享鎖,更新時再申請排他鎖,由於當用戶申請排他鎖時,其餘事務可能又已經得到了相同記錄的共享鎖,從而形成鎖衝突,甚至死鎖。

死鎖

一般來講,死鎖都是應用設計的問題,經過調整業務流程、數據庫對象設計、事務大小,以及訪問數據庫的SQL語句,絕大部分死鎖均可以免。介紹幾種避免死鎖的經常使用方法。

  • (1)在應用中,若是不一樣的程序會併發存取多個表,應儘可能約定以相同的順序來訪問表,這樣能夠大大下降產生死鎖的機會。
  • (2)在程序以批量方式處理數據的時候,若是事先對數據排序,保證每一個線程按固定的順序來處理記錄,也能夠大大下降出現死鎖的可能。好比兩個會話讀取前十個用戶的信息,每次讀取一個,那麼咱們能夠規定他們從第一個用戶開始讀,而不是倒序,這樣不會死鎖。
  • (3)在事務中,若是要更新記錄,應該直接申請足夠級別的鎖,即排他鎖,而不該先申請共享鎖,更新時再申請排他鎖,由於當用戶申請排他鎖時,其餘事務可能又已經得到了相同記錄的共享鎖,從而形成鎖衝突,甚至死鎖。 (4) 選擇合理的事務大小,小事務發生鎖衝突的概率也更小;   

若是出現死鎖,能夠用SHOW INNODB STATUS命令來肯定最後一個死鎖產生的緣由。返回結果中包括死鎖相關事務的詳細信息,如引起死鎖的SQL語句,事務已經得到的鎖,正在等待什麼鎖,以及被回滾的事務等。

死鎖的發生與否,並不在於事務中有多少條SQL語句,死鎖的關鍵在於:兩個(或以上)的Session加鎖的順序不一致。而使用本文上面提到的,分析MySQL每條SQL語句的加鎖規則,分析出每條語句的加鎖順序,而後檢查多個併發SQL間是否存在以相反的順序加鎖的狀況,就能夠分析出各類潛在的死鎖狀況,也能夠分析出線上死鎖發生的緣由。

問題:

按索引項來加鎖的話,不一樣索引相同行,會不會同時得到不一樣的鎖卻操做同一行

  • 聚簇索引也會加鎖,也就是主鍵會加鎖,這樣就防止併發修改了

自增鎖,是語句級的鎖,若是當前事務先獲取鎖,卻後執行完,在從庫按語句複製的話,會不會出現ID不一致

InnoDB的鎖實現

lock0types.h   事務鎖系統的類型定義,包含了 lock_mode定義

lock0priv.ic   鎖模塊內部的一些方法,被用於除了lock0lock.cc的三個文件裏,

lock_get_type_low 獲取鎖是表鎖仍是行鎖

lock_clust_rec_some_has_impl 檢查一行數據上是否有隱示的x鎖
lock_rec_get_n_bits 獲取一個記錄鎖的鎖位圖的bit數目
lock_rec_set_nth_bit 設置第n個記錄鎖bit位爲true
lock_rec_get_next_on_page 獲取當前page上的下一個記錄鎖
lock_rec_get_next_on_page_const 
lock_rec_get_first_on_page_addr 獲取當前page上第一個記錄鎖
lock_rec_get_first_on_page
lock_rec_get_next  返回當前記錄上的下一個顯示鎖請求的鎖
lock_rec_get_next_const
lock_rec_get_first 獲取當前記錄上的第一個顯示鎖請求的鎖
lock_rec_get_nth_bit    Gets the nth bit of a record lock.
lock_get_mode  獲取一個鎖的 lock_mode
lock_mode_compatible  判斷兩個lock_mode是否兼容
lock_mode_stronger_or_eq 判斷lock_mode 1 是否比 lock_mode 2更強
lock_get_wait 判斷一個鎖是否是等待鎖
lock_rec_find_similar_on_page  查找一個合適的鎖結構在當前事務當前頁面下???找到的話就不用新建立鎖結構???
lock_table_has  檢查一個事務是否有指定類型的表鎖,只能由當前事務調用

lock0priv.h  鎖模塊內部的結構和方法

struct lock_table_t   表鎖結構
struct lock_rec_t   行鎖結構
struct lock_t 鎖通用結構
static const byte lock_compatibility_matrix[5][5]  鎖的兼容關係
static const byte lock_strength_matrix[5][5] 鎖的強弱關係
enum lock_rec_req_status 記錄鎖請求狀態
struct RecID  記錄鎖ID
class RecLock   記錄鎖類
add_to_waitq   入隊一個鎖等待
create   爲事務建立一個鎖並初始化
is_on_row  Check of the lock is on m_rec_id.
lock_alloc 建立鎖實例
prepare 作一些檢查個預處理爲建立一個記錄鎖
mark_trx_for_rollback 收集須要異步回滾的事務
jump_queue   跳過全部低優先級事務並添加鎖,若是能授予鎖,則授予,不能的話把其餘都標記異步回滾
lock_add   添加一個記錄鎖到事務鎖列表和鎖hash表中
deadlock_check 檢查並解決死鎖
check_deadlock_result  檢查死鎖檢查的結果
is_predicate_lock  返回時不是predictate鎖
init 按照要求設置上下文
lock_get_type_low 返回行鎖仍是表鎖
lock_rec_get_prev 獲取一個記錄上的前一個鎖

鎖類型

在 Innodb 內部用一個 unsiged long 類型數據表示鎖的類型, 最低的 4 個 bit 表示 lock_mode, 5-8 bit 表示 lock_type, 剩下的高位 bit 表示行鎖的類型。

  • lock_type

5-8 bit 位標識 lock_type 目前只使用了兩個,第5位標識是表鎖,第6位標識是行鎖

#define LOCK_TABLE  16  /*!< table lock */ //表鎖
#define LOCK_REC    32  /*!< record lock */ //記錄鎖
  • lock_mode

lock描述了鎖的基本模式,目前有5種模式,IS、IX、S、X、AI

enum lock_mode {
    LOCK_IS = 0,    /* intention shared */
    LOCK_IX,    /* intention exclusive */
    LOCK_S,     /* shared */
    LOCK_X,     /* exclusive */
    LOCK_AUTO_INC,  /* locks the auto-inc counter of a table
            in an exclusive mode */
    LOCK_NONE,  /* this is used elsewhere to note consistent read */
    LOCK_NUM = LOCK_NONE, /* number of lock modes */
    LOCK_NONE_UNSET = 255
};

如下是鎖的基本模式的兼容關係和強弱關係

/* LOCK COMPATIBILITY MATRIX
 *    IS IX S  X  AI
 * IS +  +  +  -  +
 * IX +  +  -  -  +
 * S  +  -  +  -  -
 * X  -  -  -  -  -
 * AI +  +  -  -  -
 *
 * Note that for rows, InnoDB only acquires S or X locks.
 * For tables, InnoDB normally acquires IS or IX locks.
 * S or X table locks are only acquired for LOCK TABLES.
 * Auto-increment (AI) locks are needed because of
 * statement-level MySQL binlog.
 * See also lock_mode_compatible().
 */
static const byte lock_compatibility_matrix[5][5] = {
 /**         IS     IX       S     X       AI */
 /* IS */ {  TRUE,  TRUE,  TRUE,  FALSE,  TRUE},
 /* IX */ {  TRUE,  TRUE,  FALSE, FALSE,  TRUE},
 /* S  */ {  TRUE,  FALSE, TRUE,  FALSE,  FALSE},
 /* X  */ {  FALSE, FALSE, FALSE, FALSE,  FALSE},
 /* AI */ {  TRUE,  TRUE,  FALSE, FALSE,  FALSE}
};

/* STRONGER-OR-EQUAL RELATION (mode1=row, mode2=column)
 *    IS IX S  X  AI
 * IS +  -  -  -  -
 * IX +  +  -  -  -
 * S  +  -  +  -  -
 * X  +  +  +  +  +
 * AI -  -  -  -  +
 * See lock_mode_stronger_or_eq().
 */
static const byte lock_strength_matrix[5][5] = {
 /**         IS     IX       S     X       AI */
 /* IS */ {  TRUE,  FALSE, FALSE,  FALSE, FALSE},
 /* IX */ {  TRUE,  TRUE,  FALSE, FALSE,  FALSE},
 /* S  */ {  TRUE,  FALSE, TRUE,  FALSE,  FALSE},
 /* X  */ {  TRUE,  TRUE,  TRUE,  TRUE,   TRUE},
 /* AI */ {  FALSE, FALSE, FALSE, FALSE,  TRUE}
};
  • record_lock_type

剩下的高位標識行鎖的模式,對於表鎖這些位都是空的

目前record_lock_type有如下值

#define LOCK_WAIT   256 /*!< Waiting lock flag; when set, it  //鎖等待
                means that the lock has not yet been
                granted, it is just waiting for its
                turn in the wait queue */
/* Precise modes */
#define LOCK_ORDINARY   0   /*!< this flag denotes an ordinary
                next-key lock in contrast to LOCK_GAP
                or LOCK_REC_NOT_GAP */
#define LOCK_GAP    512 /*!< when this bit is set, it means that the
                lock holds only on the gap before the record;
                for instance, an x-lock on the gap does not
                give permission to modify the record on which
                the bit is set; locks of this type are created
                when records are removed from the index chain
                of records */
#define LOCK_REC_NOT_GAP 1024   /*!< this bit means that the lock is only on
                the index record and does NOT block inserts
                to the gap before the index record; this is
                used in the case when we retrieve a record
                with a unique key, and is also used in
                locking plain SELECTs (not part of UPDATE
                or DELETE) when the user has set the READ
                COMMITTED isolation level */
#define LOCK_INSERT_INTENTION 2048 /*!< this bit is set when we place a waiting
                gap type record lock request in order to let
                an insert of an index record to wait until
                there are no conflicting locks by other
                transactions on the gap; note that this flag
                remains set when the waiting lock is granted,
                or if the lock is inherited to a neighboring
                record */
#define LOCK_PREDICATE  8192    /*!< Predicate lock */
#define LOCK_PRDT_PAGE  16384   /*!< Page lock */

鎖結構

clipboard.png

  • lock_sys

首先是鎖系統結構,在Innodb啓動的時候初始化,在Innodb結束的時候釋放

主要保存着鎖的hash表,以及相關事務、線程的一些信息

/** The lock system struct */
struct lock_sys_t{
    char        pad1[CACHE_LINE_SIZE];  /*!< padding to prevent other
                        memory update hotspots from
                        residing on the same memory
                        cache line */
    LockMutex   mutex;          /*!< Mutex protecting the
                        locks */
    hash_table_t*   rec_hash;       /*!< hash table of the record
                        locks */
    hash_table_t*   prdt_hash;      /*!< hash table of the predicate
                        lock */
    hash_table_t*   prdt_page_hash;     /*!< hash table of the page
                        lock */
    char        pad2[CACHE_LINE_SIZE];  /*!< Padding */
    LockMutex   wait_mutex;     /*!< Mutex protecting the
                        next two fields */
    srv_slot_t* waiting_threads;    /*!< Array of user threads
                        suspended while waiting for
                        locks within InnoDB, protected
                        by the lock_sys->wait_mutex */
    srv_slot_t* last_slot;      /*!< highest slot ever used
                        in the waiting_threads array,
                        protected by
                        lock_sys->wait_mutex */
    ibool       rollback_complete;
                        /*!< TRUE if rollback of all
                        recovered transactions is
                        complete. Protected by
                        lock_sys->mutex */
    ulint       n_lock_max_wait_time;   /*!< Max wait time */
    os_event_t  timeout_event;      /*!< Set to the event that is
                        created in the lock wait monitor
                        thread. A value of 0 means the
                        thread is not active */
    bool        timeout_thread_active;  /*!< True if the timeout thread
                        is running */
};

lock_sys_create .   Creates the lock system at database start.
lock_sys_close     Closes the lock system at database shutdown
  • lock_t

不管是行鎖仍是表鎖都使用lock_t結構保存,其中用一個union來分別保存行鎖和表鎖不一樣的數據,分別爲lock_table_t和lock_rec_t

struct lock_t {
    trx_t*      trx;        /*!< transaction owning the lock */
    UT_LIST_NODE_T(lock_t)
            trx_locks;  /*!< list of the locks of the transaction */

    dict_index_t*   index;      /*!< index for a record lock */

    lock_t*     hash;       /*!< hash chain node for a record lock. The link node in a singly linked list, used during hashing. */

    union { //表鎖和記錄鎖不一樣的數據
        lock_table_t    tab_lock;/*!< table lock */   //表鎖結構體
        lock_rec_t  rec_lock;/*!< record lock */  //記錄鎖結構體
    } un_member;            /*!< lock details */ 

    ib_uint32_t type_mode;  /*!< lock type, mode, LOCK_GAP or LOCK_REC_NOT_GAP, LOCK_INSERT_INTENTION, wait flag, ORed */  //鎖類型

    /** Remove GAP lock from a next Key Lock */
    void remove_gap_lock()   //移除一個next-key鎖的gap鎖
    {
        ut_ad(!is_gap());
        ut_ad(!is_insert_intention());
        ut_ad(is_record_lock());
        type_mode |= LOCK_REC_NOT_GAP;
    }

    /** Determine if the lock object is a record lock.
    @return true if record lock, false otherwise. */
    bool is_record_lock() const   //判斷是不是記錄鎖
    {
        return(type() == LOCK_REC);
    }

    /** Determine if it is predicate lock.
    @return true if predicate lock, false otherwise. */
    bool is_predicate() const
    {
        return(type_mode & (LOCK_PREDICATE | LOCK_PRDT_PAGE));
    }

    bool is_waiting() const
    {
        return(type_mode & LOCK_WAIT);
    }

    bool is_gap() const
    {
        return(type_mode & LOCK_GAP);
    }

    bool is_record_not_gap() const
    {
        return(type_mode & LOCK_REC_NOT_GAP);
    }

    bool is_insert_intention() const
    {
        return(type_mode & LOCK_INSERT_INTENTION);
    }

    ulint type() const {
        return(type_mode & LOCK_TYPE_MASK);
    }

    enum lock_mode mode() const
    {
        return(static_cast<enum lock_mode>(type_mode & LOCK_MODE_MASK));
    }

    /** Get lock hash table
    @return lock hash table */
    hash_table_t* hash_table() const
    {
        return(lock_hash_get(type_mode));
    }

    /** Get tablespace ID for the lock
    @return space ID */
    ulint space() const
    {
        return(un_member.rec_lock.space);
    }

    /** Get page number of the lock
    @return page number */
    ulint page_number() const
    {
        return(un_member.rec_lock.page_no);
    }

    /** Print the lock object into the given output stream.
    @param[in,out]  out the output stream
    @return the given output stream. */
    std::ostream& print(std::ostream& out) const;

    /** Convert the member 'type_mode' into a human readable string.
    @return human readable string */
    std::string type_mode_string() const;

    const char* type_string() const
    {
        switch (type_mode & LOCK_TYPE_MASK) {
        case LOCK_REC:
            return("LOCK_REC");
        case LOCK_TABLE:
            return("LOCK_TABLE");
        default:
            ut_error;
        }
    }
};
 
 
/** A table lock */
struct lock_table_t {
    dict_table_t*   table;      /*!< database table in dictionary
                    cache */
    UT_LIST_NODE_T(lock_t)
            locks;      /*!< list of locks on the same
                    table */
    /** Print the table lock into the given output stream
    @param[in,out]  out the output stream
    @return the given output stream. */
    std::ostream& print(std::ostream& out) const;
};


 
 
/** Record lock for a page */
struct lock_rec_t {
    ib_uint32_t space;      /*!< space id */
    ib_uint32_t page_no;    /*!< page number */
    ib_uint32_t n_bits;     /*!< number of bits in the lock
                    bitmap; NOTE: the lock bitmap is
                    placed immediately after the
                    lock struct */

    /** Print the record lock into the given output stream
    @param[in,out]  out the output stream
    @return the given output stream. */
    std::ostream& print(std::ostream& out) const;
};
  • bitmap

Innodb 使用位圖來表示鎖具體鎖住了那幾行,在函數 lock_rec_create 中爲 lock_t 分配內存空間的時候,會在對象地址後分配一段內存空間(當前行數 + 64)用來保存位圖。n_bits 表示位圖大小。

/* Make lock bitmap bigger by a safety margin */
n_bits = page_dir_get_n_heap(page) + LOCK_PAGE_BITMAP_MARGIN;
n_bytes = 1 + n_bits / 8;

lock = static_cast<lock_t*>(
    mem_heap_alloc(trx->lock.lock_heap, sizeof(lock_t) + n_bytes));

顯示鎖和隱示鎖

explicit lock 顯示鎖
implicit lock 隱示鎖

InnoDB增長隱示鎖的目的是在INSERT的時候不加鎖

具體實現爲

  • 1.在insert的時候,不進行加鎖
  • 2.在當前讀訪問到一行的時候,判斷是否有隱示鎖且事務是活躍事務,有的話先轉爲顯示鎖

鎖流程

lock system 開始啓動 申請lock_sys_t結構,初始化結構體

lock system 結束關閉 釋放lock_sys_t結構的元素,釋放結構體

插入加鎖流程

https://www.colabug.com/32979...

問題:爲何有GAP也能插入(有GAP是不能插入的),插入意向鎖何時加(插入以前嘗試加插入意向鎖,衝突加等待,不衝突直接插數據), 有什麼用,惟一鍵衝突如何處理的(須要檢測衝突會先嚐試給行加S|next-key lock,加成功再檢測)

和加鎖有關的流程大概以下

  • 1)對錶加IX鎖
  • 2)對修改的頁面加X鎖
  • 3)若是須要檢測惟一鍵衝突,嘗試給須要加的惟一鍵列加一個S|next-key lock鎖,可能會產生鎖等待
  • 4)判斷是否插入意向鎖衝突,衝突加等待的插入意向鎖,不衝突直接插入數據
  • 5)釋放頁面鎖

clipboard.png

clipboard.png

刪除加鎖流程

刪除加鎖有個重要的問題是,刪除併發的時候的加鎖會有如下死鎖問題

  • 1)事務1獲取表IX鎖
  • 2)事務1獲取頁面X鎖
  • 3)事務1獲取第n行的x|not gap 鎖
  • 4)事務1刪除第n行
  • 5)事務1釋放頁面X鎖
  • 6)事務2獲取頁面X鎖
  • 7)事務2嘗試獲取第n行的x|not gap鎖,發現衝突,等待
  • 8)事務2釋放頁面X鎖
  • 9)事務1釋放第n行的鎖,提交事務
  • 10)釋放第n行鎖的時候,檢查到事務2有一個等待鎖,發現能夠加鎖了,喚醒事務2,成功加鎖
  • 11)事務3獲取頁面X鎖
  • 12)事務3嘗試刪除第n行,發現第n行被刪除(注意,此時記錄還在還沒被從頁面刷出),嘗試獲取第n行的next-key lock,發現事務2有一個x|gap鎖衝突,等待
  • 13)事務3釋放頁面X鎖
  • 14)事務2獲取頁面X鎖,檢查頁面是否改動,從新檢查第n行數據,發現第n行數據被刪除,嘗試獲取第n行的next-key lock,發現有事務3已經在等待這個鎖了,事務2衝突,進入等待
  • 15)死鎖

表鎖加鎖流程

lock_table

  • 1)檢查當前事務是否擁有更強的表鎖,有的話直接返回成功,不然繼續往下走
  • 2)遍歷表的鎖列表,判斷是否有衝突的鎖,若是沒有轉3,有轉4
  • 3)直接建立一個表鎖,放入事務的lock list中,放入table的 lock list中,加鎖成功
  • 4)如3步驟,建立等待的表鎖,加入list,而後進行死鎖檢測和死鎖解決,回滾當前事務或者掛起當前事務

行鎖加鎖流程

clipboard.png

  • lock_rec_lock

主要的參數是 mode(鎖類型),block(包含該行的 buffer 數據頁),heap_no(具體哪一行)。就能夠肯定加什麼樣的鎖,以及在哪一行加。

加鎖流程主要是lock fast和lock slow,首先進入lock fast進行快速加鎖,若是快速加鎖失敗則進入lock slow開始正常加鎖流程,可能有鎖衝突檢查、死鎖檢查等流程

  • lock fast
  • 1.獲取須要加鎖的頁面上第一個record lock
  • 2.判斷獲取的鎖是否是空,是轉3,否轉4
  • 3.若是須要加的是隱示鎖直接返回成功,不然,建立一個建立一個RecLock對象而後建立一個鎖返回成功
  • 4.判斷當前頁面上是否只有一個鎖,且這個鎖是當前事務的,且這個鎖模式和須要加的模式同樣,且bitmap的大小夠用,知足前述條件轉5,不然轉6
  • 5.能夠快速加鎖,直接設置bitmap進行加鎖,返回成功
  • 6.快速加鎖失敗,返回失敗並進入lock slow流程
  • lock slow
  • 1.調用lock_rec_has_expl函數判斷當前事務是否是有更強的鎖,知足轉2,不知足轉3

lock_rec_has_expl函數遍歷rec_hash,獲取事務編號是當前事務的鎖,同時知足如下五個條件就斷定爲有更強的鎖

    • 1)不是一個插入意向鎖
    • 2)不是一個等待中的鎖
    • 3)根據強弱關係矩陣判斷知足更強
    • 4)不是lock_rec_not_gap類型,或者要加的鎖是lock_rec_not_gap類型或者heap_no是上界
    • 5)不是lock_gap類型,或者要加的鎖是lock_gap類型或者heap_no是上界
    • 2.有更強的鎖,直接返回成功,什麼都不須要作
    • 3.若是沒有更強的鎖,調用lock_rec_other_has_conflicting判斷是否有鎖衝突須要等待,若是有轉4,沒有轉5。lock_rec_other_has_conflicting函數遍歷rec_hash,拿出對應行上的每個鎖,調用 lock_rec_has_to_wait 進行衝突判斷

      • 1)若是當前鎖和要加的鎖是同一個事務的,直接返回,沒有衝突
      • 2)根據兼容矩陣判斷當前鎖和要加的鎖是否兼容,若是兼容,直接返回,沒有衝突
      • 3)若是要加的是lock_gap或者heap_no是頁面上界,且不是lock_insert_intention的話,能夠直接返回,沒有衝突,由於非插入意向鎖的gap鎖是不用等待的,都不衝突
      • 4)若是要加的鎖不是插入意向鎖lock_insert_intention,且當前鎖是一個gap鎖,直接返回,沒有衝突
      • 5)若是要加的鎖是gap鎖,且當前鎖是lock_rec_not_gap鎖,直接返回,沒有衝突
      • 6)若是當前鎖是一個插入意向鎖,直接返回沒有衝突
      • 7)不知足上述條件,返回衝突
ps:爲何通過2步驟判斷鎖不兼容還須要往下走5個判斷,是由於鎖類型lock_mode/lock_type/rec_lock_type三種標記位同時有,如lock_x|lock_gap, lock_s|lock_rec_not_gap 這兩個鎖雖然lock_mode不兼容,但不衝突
  • 4.調用add_to_waitq,入隊一個鎖等待

    • 1)調用creat,建立lock_t,高優先級事務不放入rec_hash表,非高優先級放入
    • 2)若是是高優先級事務,調用jump_queue,若是加鎖成功直接返回,jump_queue大概爲跳過全部優先級比當前鎖低的等待鎖,加入等待隊列中
    • 3)調用deadlock_check進行死鎖檢測
  • 5.判斷是不是隱示鎖,是的話直接返回成功,什麼都不作,不是的話調用lock_rec_add_to_queue入隊一個鎖

    • 1)type_mode|=lock_rec,判斷heap_no是不是頁面上屆,是的話,type_mode不能是lock_rec_not_gap
    • 2)遍歷rec_hash判斷當前行上是否有等待的鎖,沒有轉3,有轉4
    • 3)若是沒有,且當前鎖不是一個lock_wait,尋找當前頁面上有沒有類似的鎖(當前事務的鎖且鎖類型和要加的鎖同樣),有的話直接設置標記位,沒有轉4
    • 4)建立一個鎖lock_t,設置bit位,設置lock_type等信息,添加到rec_hash表中和事務的lock_list中

釋放鎖流程

clipboard.png

lock_rec_dequeue_from_page

  • 1.把當前鎖從全局hash表中刪除
  • 2.把當前鎖從事務鎖list中刪除
  • 3.調用lock_rec_grant函數,嘗試給等待鎖加鎖

    • 1)遍歷鎖hash表中當前頁面上的鎖,對於每一個等待鎖,調用lock_rec_has_to_wait_in_queue函數判斷是否還須要等待

lock_rec_has_to_wait_in_queue

- 遍歷當前鎖所在頁面的全部非等待鎖
       - 對於每一個鎖,根據heap_no判斷當前鎖須要加鎖的行是否被鎖上
       - 若是被鎖上,判斷要加的鎖是否須要等待
- 2)對於不須要等待的鎖,調用lock_grant進行加鎖
       - 移除lock的lock_wait狀態
       - 置空lock的wait_lock
       - 調用que_thr_end_lock_wait和lock_wait_release_thread_if_suspended喚醒等待中的線程

lock_table_dequeue

  • 1.調用lock_table_remove_low

    • 1)若是是自增加鎖,將鎖從事務的autoinc_locks list中移除
    • 2)從事務鎖列表將鎖移除
    • 3)從表的鎖列表將鎖移除
  • 2.遍歷當前表的表鎖列表,判斷等待的鎖是否能加鎖,能的話調用lock_grant函數進行加鎖

死鎖流程

clipboard.png

構造wait-for graph

構造一個有向圖,圖中的節點表明一個事務,圖的一個邊A->B表明着A事務等待B事務的一個鎖

具體實現是在死鎖檢測時,從當前鎖的事務開始搜索,遍歷當前行的全部鎖,判斷當前事務是否須要等待現有鎖釋放,是的話,表明有一條邊,進行一次入棧操做

死鎖檢測

有向圖判斷環,用棧的方式,若是有依賴等待,進行入棧,若是當前事務全部依賴的事務遍歷完畢,進行一次出棧

回滾事務選擇

若是發現循環等待,選擇當前事務和等待的事務其中權重小的一個回滾,具體的權重比較函數是 trx_weight_ge, 若是一個事務修改了不支持事務的表,那麼認爲它的權重較高,不然認爲 undo log 數加持有的鎖數之和較大的權重較高。

DeadlockChecker::search()

  • 1)將當前要加的等待鎖設置爲wait_lock,start_lock
  • 2)遍歷wait_lock鎖所在位置上的全部鎖
  • 3)判斷wait_lock鎖是否須要等待遍歷到的鎖lock,不須要等待就跳過,
  • 4)若是須要等待,將lock push進棧,同時將lock設置爲wait_lock,搜索深度+1,重複上述過程
  • 5)若是要等待,判斷lock和start_lock是不是一個事務,是的話,發生死鎖,選擇當前事務和start_lock的事務權重小的回滾
  • 6)若是要等待,判斷搜索深度是否已通過深,超過閾值,認爲發生死鎖,直接回滾了start_lock的事務

等待與喚醒
鎖的等待以及喚醒其實是線程的等待和喚醒,調用函數 lock_wait_suspend_thread 掛起當前線程,配合 OS_EVENT 機制,實現喚醒和鎖超時等功能

鎖分裂、合併、遷移

分裂

索引頁面分裂致使的鎖分裂

  • 1)普通鎖,會跟隨着記錄位置的移動,鎖一塊兒移動到新的位置,加鎖信息保持不變
  • 2)gap鎖,會給新頁面和老頁面的上下界最大最小值上根據狀況調整gap鎖,目的保持gap鎖上的區間保持不變

合併

索引頁面合併致使的鎖合併

  • 合併和分裂基本一致

遷移

插入和刪除記錄時的GAP鎖的遷移

  • 1.在有GAP鎖的間隙裏插入記錄時會出現GAP鎖的遷移

主要是出如今當前事務擁有一個記錄的GAP鎖,又在這個記錄前插入記錄時

set global tx_isolation='repeatable-read';

create table t1(c1 int primary key, c2 int unique) engine=innodb;
insert into t1 values(1,1);
begin;
# supremum 記錄上加 LOCK_X|LOCK_GAP 鎖住(1~)
select * from t1 where c2=2 for update;
# 發現插入(3,3)的間隙存在GAP鎖,所以給(3,3)加LOCK_X | LOCK_GAP鎖。這樣依然鎖住了(1~)
insert into t1 values(3,3);
for (lock = lock_rec_get_first(lock_sys->rec_hash, block, heap_no);
         lock != NULL;
         lock = lock_rec_get_next(heap_no, lock)) {  //遍歷當前行的全部鎖

        if (!lock_rec_get_insert_intention(lock)
            && (heap_no == PAGE_HEAP_NO_SUPREMUM
            || !lock_rec_get_rec_not_gap(lock))) {  //若是不是插入意向鎖 且 heap是上界或者不是一個非GAP鎖

            lock_rec_add_to_queue(  //添加一個GAP的且mode和lock一致的鎖到下一行
                LOCK_REC | LOCK_GAP | lock_get_mode(lock),
                block, heir_heap_no, lock->index,
                lock->trx, FALSE);
        }
    }
  • 2.刪除擁有GAP鎖的記錄時會出現GAP鎖的遷移

若有記錄 1 3 5

  • 1)事務A對記錄3加GAP鎖,阻止1-3的間隙插入
  • 2)事務B對記錄3加X鎖,GAP鎖和X鎖不衝突,加鎖成功,事務B刪除記錄3,提交
  • 3)此時當後臺線程刷盤時,發現記錄3已經刪除,將今後頁面將3記錄刪除,但發現3上還有個GAP鎖,就會把這個GAP鎖繼承給這個記錄後面的記錄5

clipboard.png

Innodb在RR和RC隔離下的加鎖實例分析

例子:select * from meng_hinata where id = 10 for update

組合一:id列是主鍵,RC隔離級別

在主鍵id=10列加上X鎖

組合二:id列是二級惟一索引,RC隔離級別

在惟一索引id=10列上加X鎖,在主鍵索引上對應列加X鎖

組合三:id列是二級非惟一索引,RC隔離級別

在二級索引上全部id=10列加上X鎖,這些列對應的主鍵索引列加上X鎖

組合四:id列上沒有索引,RC隔離級別

在聚簇索引上掃描,全部列上加X鎖,此處有個優化,不知足的列在加鎖後,判斷不知足便可釋放鎖,違背二階段加鎖

組合五:id列是主鍵,RR隔離級別

在主鍵id=10列上加X鎖

組合六:id列是二級惟一索引,RR隔離級別

在惟一索引id=10列上加X鎖,在主鍵索引上對應列加X鎖

組合七:id列是二級非惟一索引,RR隔離級別

在二級索引上查找id=10列,找到則加上X鎖和GAP鎖,而後對應的聚簇索引列加上X鎖,最後一個不知足的列只會加上GAP鎖

組合八:id列上沒有索引,RR隔離級別

在聚簇索引上掃描,全部列加上X鎖和GAP鎖

測試

Innodb默認事務隔離級別爲RR

clipboard.png

看出默認隔離級別爲Repeatable Read,對user_id = 100000745進行count並顯示加一個X鎖,使用explain看出使用了uid索引即user_id字段的索引

clipboard.png

id = 1449731912的數據user_id = 100000745,能夠看出在對user_id字段索引加了X鎖以後,操縱相對應的主鍵索引時也會被阻塞,驗證了對非主鍵索引加X鎖的同時會對相應主鍵索引也加鎖

clipboard.png

同時在對user_id字段索引加了X鎖以後,也不能插入user_id相同的新數據,驗證了innodb再RR隔離級別下也是防止了幻讀的現象,實際上當範圍查找時也會再加一個間隙鎖來保證不會有幻讀

clipboard.png

clipboard.png

能夠看出不使用for update以後變爲非當前讀,也沒有進行加鎖,能夠進行插入操做。

clipboard.png

clipboard.png

以上兩個圖設置隔離級別爲RC後,能夠看出在user_id = 100000745 進行for update查詢後,仍是能進行插入相同user_id值的列,說明只加了X鎖並無加間隙鎖,同時由於X鎖的緣由,不能進行刪除,驗證了innodb引擎在RC隔離級別對於當前度也是會對讀到的信息加鎖,但沒加間隙鎖,會出現幻讀。

clipboard.png

clipboard.png

以上兩個圖,在RC隔離級別下。

首先事務A對無索引的列進行查找並加鎖,會掃描全表,注意並無加表鎖,而是對全部行都加了X鎖,可是沒加間隙鎖,事務B仍是能夠插入。

此時事務A再掃描全表並加X鎖,發現被阻塞,提交事務B後繼續運行

事務A對全表加X鎖後,事務B再次嘗試插入,一樣成功,但沒法刪除數據,說明仍是沒用表鎖,對全部行加了X鎖。

InnoDB鎖同步機制

InnoDB條件變量核心數據結構爲os_event_t,相似pthread_cont_t。若是須要建立和銷燬則分別使用os_event_create和os_event_free函數。須要等待某個條件變量,先調用os_event_reset,而後使用os_event_wait,若是須要超時等待,使用os_event_wait_time替換os_event_wait便可

InnoDB自旋互斥鎖的實現主要在文件 sync0sync.cc 和sync0sync.ic 中,頭文件sync0sync.h 定義了核心數據結構ib_mutex_t。使用方法很簡單,mutex_create建立鎖,mutex_free釋放鎖,mutex_enter嘗試得到鎖,若是已經被佔用了,則等待。mutex_exit釋放鎖,同時喚醒全部等待的線程,拿到鎖的線程開始執行,其他線程繼續等待。

InnoDB讀寫鎖的核心實如今源文件sync0rw.cc 和sync0rw.ic 中,核心數據結構rw_lock_t 定義在sync0rw.h 中。使用方法與InnoDB 自旋互斥鎖很相似,只不過讀請求和寫請求要調用不一樣的函數。加讀鎖調用rw_lock_s_lock, 加寫鎖調用rw_lock_x_lock,釋放讀鎖調用rw_lock_s_unlock, 釋放寫鎖調用rw_lock_x_unlock,建立讀寫鎖調用rw_lock_create,釋放讀寫鎖調用rw_lock_free。函數rw_lock_x_lock_nowait和rw_lock_s_lock_nowait表示,當加讀寫鎖失敗的時候,直接返回,而不是自旋等待。

相關文章
相關標籤/搜索