MySQL鎖(樂觀鎖、悲觀鎖、多粒度鎖)

2022-06-06 12:05:17

並行事務可能出現的情況:

  • 讀-讀事務並行:此時是沒有問題的,讀操作不會對記錄又任何影響。

  • 寫-寫事務並行:並行事務相繼對相同的記錄做出改動,因為寫-寫並行可能會產生髒寫的情況,但是沒有一個隔離級別允許髒寫的情況發生。MySQL使用鎖的機制來控制並行情況下讓事務對一條記錄進行排隊修改,只有對記錄修改的事務提交了才能讓下一個事務對記錄進行修改。

    當第一個事務嘗試對一條記錄進行修改。會和記錄行關聯一個鎖結構。

trx資訊: 代表鎖結構是哪個事務產生的。

is_waiting:false代表擁有記錄的修改權,true表示等待鎖資源釋放。

​ 當第二個事務嘗試獲得鎖,失敗也會建立一個鎖結構將is_waiting置為true,並填入事務資訊,加入記錄的鎖結構中。

​ 當第一個事務提交結束釋放鎖資源,並會喚醒下一個事務將其等待狀態設定為false,讓其其獲得鎖資源。

  • 讀-寫事務並行:有兩種解決方案
    • MVCC(多版本並行控制) + 加鎖。即上文我們說過MVCC只能用作查詢資料,所以我們使用MVCC來解決並行下事務對記錄修改同時的讀取不出現髒讀、不可重複讀和幻讀。寫操作就是用加鎖的方式來進行控制。
    • 讀、寫都使用加鎖。這樣子讀寫的話,讀和寫都要進行加鎖,相當於讀和寫操作之間要像寫和寫一樣進行排隊。

一致性讀

事務利用MVCC進行讀取操作可以被稱為一致性讀、一致性無鎖讀又或者快照讀。一致性讀不會產生加鎖操作。MVCC我們之前文章都講過

鎖定讀

就是我們讀寫都是用加鎖的操作。

學習Java的JUC的時候,對於讀寫鎖就是差不多的。

  • 共用鎖 Shared Locks,簡稱S鎖。要讀取一條記錄時,需要先獲取S鎖
  • 獨佔鎖 Exclusive Locks,簡稱X鎖。當我們需要更改一條記錄時,需要獲取記錄的X鎖。

S鎖是可以共用的,即多個事務之間共同獲取S鎖。但是X鎖只能被一個事務擁有,直到事務提交然後才能釋放。

不同寫操作加鎖過程

  • delete,會先獲取記錄的X鎖,然後delete_mark 為1,然後提交事務釋放鎖,放入垃圾連結串列就OK。
  • update
    • 如果沒更新主鍵,且更新後資料行各列的大小不發生改變,就直接獲取X鎖,然後更新資料即可。
    • 如果沒更新主鍵,且更新後資料行各列的大小發生改變,先獲取記錄的X鎖,然後將其徹底刪除,然後插入更新的記錄,新插入的記錄由隱式鎖進行保護。
    • 如果更新了主鍵,就直接先獲取X鎖,然後先delete的流程來一下,然後insert的流程來一下,都是加的X鎖。
  • insert,直接插入新資料,由隱式鎖來保證並行事務安全。

多粒度鎖

上面說的都是行鎖,粒度較細,我們還可以加一個粒度較大的鎖,表級鎖。

  • 共用鎖,簡稱S鎖,對錶加S鎖,其他事務可以繼續對錶或者行加S鎖。但是如果想對錶加X鎖,或者表內的行加X鎖,就需要進行排隊等待表的S鎖釋放。
  • 獨佔鎖,簡稱X鎖,對錶加X鎖,其他事務就不能對錶或者行加S鎖或X鎖了,只能排隊。

但是會出現一種情況,就是表內行加了行級鎖,但是我們想對錶加表級鎖,我們怎麼才能知道表內有行級鎖呢?不能一條一條遍歷吧。

  • 意向共用鎖,簡稱IS鎖,當有事務需要對一條行記錄加S鎖時,先對錶加一個IS鎖。
  • 意向獨佔鎖,簡稱IX鎖,當有事務需要對一條行記錄加X鎖時,先對錶加一個IX鎖。

IS鎖和IX鎖的作用就是為了讓我們快速知道,表內行記錄中是否有加了鎖,是否能加表級鎖。

Innodb中的鎖

表級鎖

  • S鎖,X鎖
  • IS鎖,IX鎖
  • AUTO_INC鎖,這個鎖是用在自增欄位的自增當中的。
    • 如果我們不能確定插入的數量,即當我們使用insert ... select 、replace ... select、load data等語句,我們無法確定插入的條數是多少,innodb會使用AUTO_INC鎖。這個鎖是會在表的層面進行加鎖,然後會對插入的每條記錄進行分配一個自增值。
    • 如果我們是能夠確定的插入數量,比如insert into x(a) values('ss'),('dd');我們可以確定插入的記錄是兩條。innodb就會採用一個輕量級鎖,在為插入語句分配好這個自增列的值後,就會將其釋放。

在innodb中維護了一個系統變數innodb_autoinc_lock_mode的變數。

當值為0時就是直接採用AUTO_INC鎖,不管確不確定。

當值為1時就是採用兩種混合的方式,也就是上述的方式。

當值為2時就是一律採用輕量級鎖的方式,可能會造成不同事務的自增列產生的值是交叉的,在主從中是不安全的。不是很理解,沒有弄過主從。

行級鎖

  • Record Locks:當我們要對某個資料行進行操作,我們就會向資料行中加入這個鎖。官方命名:LOCK_REC_NOT_GAP。分為S鎖和X鎖這種型別的鎖。解決了並行事務之間對一條記錄的讀取和修改。可以解決髒寫、髒讀、不可重複讀。

  • Gap Locks:但是出現幻讀怎麼解決呢?Gap鎖就是為了解決幻讀。Gap鎖能夠防止當前記錄和前一個記錄之間的間隙不能插入新資料。當新資料發現下一條資料有gap鎖,就不會執行插入。

    我們要讓查詢的區間不要讓其他事務進行插入資料,以防止出現幻讀的情況。就會在第一次查詢的時候,比如我們查詢記錄(2,8)就會在8號記錄加一個gap鎖,這樣如果我們插入4號記錄就無法插入。如果我們要控制(2,+∞),就會在資料頁的Supremun的資料行加入gap鎖。

  • key-next Locks:就是Record和Gap鎖的合體,就能控制當前行,並且當前行和前一條的資料行之間的間隙不能插入新資料。

  • Insert Intention Locks: 可以叫做插入意向鎖,就是當前如果記錄的下一條有gap或key-next鎖,就不能插入,此時就會在記憶體中生成一個鎖結構,表示某個間隙想要插入新紀錄,正在等待,儲存在下一條的資料行中。

插入意向鎖就是JUC的一個非公平的AQS啊,就是他不會阻止別的事務繼續獲得資料行的其他鎖,就是這個插入意向鎖可能會一直一直等下去。

  • 隱式鎖。即我們上面講的,在事務進行插入的時候如果有gap鎖就會加個插入意向鎖事務進入等待狀態,但是如果沒有的話,我們就會直接進行插入。導致的結果就是什麼,我們插入的資料沒有進行加鎖保護,這時其他事務的可以直接select或者更新,直接導致髒讀,或者髒寫。

    幻讀已經被Gap鎖解決了所以不會出現哈,這裡是在可以插入的時候可能出現的錯誤,因為如果有Gap說明別的事務可能會讀取資料,不能插入,只有提交了Gap鎖取消了才能進行插入,隱式鎖是在這時候發揮作用。

    所以在資料行插入到頁中時,會判斷插入資料行的事務的trx_id是否還是活躍的狀態,如果還是活躍事務,就會為當前插入的資料行建立一個X鎖。如果不活躍了,表示事務已經提交就可以放心去改和去查了。

    在聚簇索引中就是這個流程,但是在二級索引中資料行沒有維護一個trx_id,我們就需要判斷二級索引中維護的PAGE_MAX_TRX_ID即最大修改二級索引的事務號,如果小於最小活躍事務ID,就可以放心改了,否則需要回表然後進行一遍聚簇索引的流程。???