動態

詳情 返回 返回

Mysql工作面試老大難——鎖 - 動態 詳情

解決併發事務帶來問題的兩種基本方式

併發事務訪問相同記錄的情況可以劃分為3種。

  • 讀 - 讀情況:併發事務相繼續讀取相同的記錄。讀取操作本身不會對記錄有任何影響,不會引起什麼問題,所以允許這種情況的發生。
  • 寫 - 寫情況:併發事務相繼對相同的記錄進行改動。
  • 讀 - 寫或寫 - 讀情況:也就是一個事務進行讀取操作,另一個事務進行改動操作。

寫 - 寫情況

在寫 - 寫情況下會發生髒寫的現象,任何一種隔離級別都不允許這種現象的發生。所以在多個未提交事務相繼對一條記錄進行改動時,需要讓他們排隊執行。這個排隊執行的過程其實是通過為該記錄加鎖來實現的。這個 “鎖” 本質上是一個內存中的結構,在事務執行之前本來是沒有鎖的,也就是説一開始是沒有鎖結構與記錄進行關聯的,如下圖所示。
image.png

當一個事務想對這條記錄進行改動時,首先會看看內存中有沒有這條記錄關聯的鎖結構;如果沒有,就會在內存中生成一個鎖結構與之關聯。比如,事務T1要對這條記錄進行改動,就需要生成一個鎖結構與之關聯。比如,事務T1要對這條記錄進行改動,就需要生成一個鎖結構與之關聯,如圖下所示。

image.png

其實鎖中有很多信息,不過為了方便理解,我們現在只把兩個比較重要的屬性拿了出來。

  • trx信息:表示這個鎖結構是與哪個事務關聯的。
  • is_waiting:表示當前事務是否在等待。

如圖上所示,在事務T1改動這條記錄前,就生成了一個鎖結構與該記錄關聯。因為之前沒有別的事務為這條記錄加鎖,所以is_waiting屬性就是false。我們把這個場景稱為獲取鎖成功,或者加鎖成功,然後就可以繼續執行操作了。

在事務T1提交之前,另一個事務T2也想對該記錄進行改動,那麼T2先去看看有沒有鎖結構與這條記錄關聯。在發現有一個鎖結構與之關聯後,T2也生成了一個鎖結構與這條記錄關聯,不過鎖結構的is_waiting屬性值為true,表示需要等待。我們把這個場景稱為獲取鎖失敗,或者加鎖失敗,或者沒有成功地獲取到鎖,如下所示。

image.png

事務T1提交之後,就會把它生成的鎖結構釋放掉,然後檢測一下還有沒有與該記錄關聯的鎖結構。如果發現了事務T2還在等待獲取鎖,所以把事務T2對應的鎖結構的is_waiting屬性設置為false,然後把該事務對應的線程喚醒,讓T2繼續執行。此時事務T2就算獲取到鎖了。

image.png

讀 - 寫或寫 - 讀情況

在讀 - 寫或寫 - 讀情況下會出現髒讀、不可重複度、幻讀的現象,怎麼避免髒讀、不可重複度、幻讀這些現象呢?其實有兩種可選的解決方案。

  • 方案1: 讀操作使用多版本併發控制(MVCC),寫操作進行加鎖。
    MVCC就是通過生成一個ReadView,然後通過ReadView找到符合條件的記錄版本(歷史版本由undo日誌構建的)。其實就像是在生成ReadView的那個時刻,時間靜止了(就像用相機拍了一個快照),查詢語句只能讀到在生成ReadView之前已經提交事務所做的更改,在生成ReadView之前為提交的事務或者之後才開啓的事務所做的更改是看不到的。寫操作肯定針對的是最新版本的記錄,讀記錄的歷史版本和改動記錄的最新版本這兩者並不衝突,也就是採用MVCC時,讀 - 寫操作並不衝突
  • 方案2: 讀、寫操作都採用加鎖的方式。
    如果我們的一些業務中不允許讀取記錄的就版本,而是每次都必須去讀取記錄的最新版本。比如在銀行存款的事務中,我們需要先把賬户的餘額讀出來,然後將其加上本次存款的數額,最後再寫到數據庫中。再將賬户餘額讀取出來後,就不想讓別的事務 再訪問到餘額,直到本次存款事務執行完成後,其他事務才可以訪問賬户的餘額。這樣在讀取記錄的時候也就需要對其進行加鎖操作,這也就意味着讀操作和寫操作也得想寫 - 寫操作那樣排隊執行。

很明顯,如果採用MVCC方式,讀 - 寫操作彼此並不衝突,性能更高;如果採用加鎖的方式,讀 - 寫操作彼此需要排隊執行,從而影響性能。但是在某些特殊的業務場景中,要求必須採用加鎖的方式執行,那也是沒有辦法的事。

一致性讀

事務利用MVCC進行讀取操作稱為一致性讀(Consistent Read),或者一致性無鎖讀(有的資料也稱之為快照讀)。所有普通的SELECT語句(plain SELECT)在READ COMMITTED、REPEATABLE READ隔離級別下都算是一致性讀,比如:

select * from t;
select * from t1 inner join t2 on t1.col1 = t2.col2;

一致性讀並不會對錶中的任何記錄進行加鎖操作,其他事務可以自由地對錶中的記錄進行改動。

鎖定讀

1.共享鎖和獨佔鎖

併發事務的讀 - 讀情況並不會引起什麼問題,不過對於寫 - 寫、讀 - 寫或寫 - 讀這些情況,可能會引起一些問題,需要使用MVCC或者加鎖的方式來解決它們。在使用加鎖的方式來解決問題時,由於既要允許讀 - 讀情況不受影戲那個,又要使寫 - 寫、讀 - 寫或寫 - 讀情況中的操作相互阻塞,所以設計MySQL的大叔給鎖分了個類。

  • 共享鎖(Shared Lock):簡稱S鎖。在事務要讀取一條記錄時,需要先獲取該記錄的S鎖。
  • 獨佔鎖(Exclusive Lock):也常稱為排他鎖,簡稱X鎖。在事務要改動一條記錄時,需要先獲取該記錄的X鎖。

假如事務T1首先獲取了一條記錄的S鎖,之後事務T2接着也要訪問這條記錄:

  • 如果事務T2想要再獲取一個記錄的S鎖,那麼事務T2也會獲得該鎖,這也意味着事務T1和T2在該記錄上同事持有S鎖;
  • 如果事務T2想要再獲取一個記錄的X鎖,那麼此操作會被阻塞,直到事務T1提交之後將S鎖釋放掉為止。

如果事務T1首先獲取了一條記錄的X鎖,那麼之後無論事務T2是想獲取該記錄的S鎖還是X鎖,都會被阻塞,直到事務T1提交之後將X鎖釋放掉為止。
所以S鎖和S鎖是兼容的,S鎖和X鎖是不兼容的,X鎖和X鎖也是不兼容的。我們通過下表來表示一下。

兼容性 X鎖 S鎖
X鎖 不兼容 不兼容
S鎖 不兼容 兼容

2.鎖定讀的語句

前面説到,為了採用加鎖的方式避免髒讀、不可重複讀和幻讀這些現象,在讀取一條記錄時需要獲取該記錄的S鎖。這其實是不嚴謹的,有時候我們想在讀取記錄時就獲取記錄的X鎖,從而禁止別的事務讀寫該記錄。我們把這種在讀取記錄時就為該記錄加鎖的讀取方式稱為鎖定讀(Locking Read)。設計MySQL的大叔提供了下面兩種特殊的SELECT語句格式來支持鎖定讀。

  • 對讀取的記錄加S鎖:

    select ... lock in share mode;

    也就是在普通的SELECT語句後面加上LOCK IN SHARE MODE。如果當前事務執行了該語句,那麼它會為讀取到的記錄加S鎖,這樣可以允許別的事務繼續獲取這些記錄的S鎖(比如,別的事務也適用SELECT ... LOCK IN SHARE MODE語句來讀取這些記錄時),但是不能獲取這些記錄的X鎖(比如使用SELECT ...FOR UPDATE語句來讀取這些記錄,或者直接改動這些記錄時)。如果別的事務想要獲取這些記錄的X鎖,那麼它們會被阻塞,直到當前事務提交之後將這些記錄上的S鎖釋放掉為止。

  • 對讀取的記錄加X鎖:

    select ... for update;

    也就是在普通的SELECT語句後面加上FOR UPDATE。如果當前事務執行了該語句,那麼它會為讀取到的記錄加X鎖,這樣既不允許別的事務獲取這些記錄的S鎖(比如別的事務使用SELECT ...LOCK IN SHARE MODE語句來讀取這些記錄時),也不允許獲取這些記錄的X鎖(比如説使用SELECT ...FOR UPDATE語句來讀取這些記錄,或者直接改動這些記錄時)。如果別的事務想要獲取這些記錄的S鎖或者X鎖,那麼它們會被阻塞,直到當前事務提交之後將這些記錄上的X鎖釋放掉為止。

寫操作

平時用到的寫操作無非是DELETE、UPDATE、INSERT這三種。

  • DELETE:對一條記錄執行DELETE操作的過程其實是在B+樹中定位到這條記錄的位置,然後獲取這條記錄的X鎖,最後再執行delete mark操作。我們也可以把這個 "先定位待刪除記錄在B+樹中的位置,再獲取這條記錄的X鎖的過程" 看成是一個獲取X鎖的鎖定讀。
  • UPDATE:在對一條記錄進行UPDATE操作時分為下面3種情況。

    • 如果未修改改記錄的鍵並且更新的列所佔用的存儲空間在修改前後未發生變化,則先在B+樹中定位到這條記錄,然後再獲取記錄的X鎖,最後在原記錄的位置進行修改操作。其實也可以把這個 "先定位待修改記錄在B+樹種的位置,然後再獲取記錄的X鎖的過程" 看成是一個獲取X鎖的鎖定讀。
    • 如果未修改改記錄的鍵值並且至少有一個被更新的列佔用的存儲空間在修改前後發生變化,則先在B+樹中定位到這條記錄的位置,然後獲取記錄的X鎖,之後將該記錄徹底刪除掉(就是把記錄徹底移入垃圾鏈表),最後再插入一條新記錄。可以把這個 “先定位待修改記錄在B+樹中的位置,然後再獲取記錄的X鎖的過程” 看成是一個獲取X鎖的鎖定讀,與被徹底刪除的記錄關聯的鎖也會被轉移到這條新插入的記錄上來。
    • 如果修改了改記錄的鍵值,則相當於在原記錄上執行DELETE操作之後再來一次INSERT操作,加鎖操作就需要按照DELETE和INSERT的規則進行了。
  • INSERT:一般情況下,信插入的一條記錄受隱式鎖保護,不需要在內存中為其生成對應的鎖結構。

多粒度鎖

上文提到的鎖都是針對記錄的,可以將其稱為行級鎖或者行鎖。一條記錄加行鎖,影響的也只是這條記錄而已,我們就説這個行鎖的粒度比較細。其實一個事務也可以在表級別進行加鎖,自然就將其稱為表級鎖或者表鎖。對一個表加鎖,會影響表中所有的記錄,我們就説這個鎖的粒度比較粗。給表加的鎖也可以分為共享鎖(S鎖)和獨佔鎖(X鎖)。

  • 給表加S鎖
    如果一個事務給表加了S鎖,那麼:

    • 別的事務可以繼續獲得該表的S鎖;
    • 別的事務可以繼續獲得該表中某些記錄的S鎖;
    • 別的事務不可以繼續獲得該表的X鎖;
    • 別的事務不可以繼續獲得該表中某些記錄的X鎖。
  • 給表加X鎖
    如果一個事務給表加了X鎖(意味着該事務要獨佔這個表),那麼:

    • 別的事務部不可以繼續獲得該表的S鎖;
    • 別的事務部不可以繼續獲得該表中某些記錄的S鎖;
    • 別的事務不可以繼續獲得該表的X鎖;
    • 別的事務部不可以繼續獲得該表中某些記錄的X鎖。

上面的文字看着有點囉嗦。為了更好的理解這個表級別的S鎖和X鎖,我們以大學教學樓中的教室為例來分析加鎖的情況。

  • 教室一般都是公用的,我們可以隨便選一間教室進去上自習。當然,教室不是自家的,一間教室可以容納很多同學上自習。每當一個同學進去上自習,就相當於在教室門口掛了一把S鎖,如果很多同學都進去上自習,就相當於教室門口掛了很多把S鎖(類似行級別的S鎖)。
  • 有時教室會進行檢修,比如換地板、換天花板、換燈管啥的,這些維修項目並不能同時開展。如果教室針對某個項目進行檢修,就不允許同學來上自習,也不允許其他維修項目進行,此時相當於教室門口掛了一把X鎖(類似行級別的X鎖)。

上面提到的這兩種鎖都是針對教室而言,不過我們有事會有一些特殊的需求。

  • 有上級領導要來參觀教學樓的環境。校領導不想影響同學們上自習,但是此時不能有教室處於維修狀態,於是可以在教學樓門口放一把S鎖(類似於表級別的S鎖)。此時:

    • 來上自習的學生看到教學樓門口有S鎖,可以繼續進入教學樓上自習;
    • 修理工看到教學樓門口有S鎖,則先在教學樓門口等着,等啥時候上級領導走了,把教學樓的S鎖撤掉後,再進入教學樓維修。
  • 學校要佔用教學樓進行考試。此時不允許教學樓中有正在上自習的教室,也不允許對教室進行維修,於是可以在教學樓門口放置一把X鎖(類似表級別的X鎖)。此時:

    • 來上自習的學生看到教學樓門口有X鎖,則需要在教學樓門口等着,啥時候考試結束,把教學樓的X鎖撤掉後,再進入教學樓上自習。
    • 修理工看到教學樓門口有X鎖,則先在教學樓門口等着,等啥時候考試結束,把教學樓的X鎖車掉後,再進入教學樓維修。

但是這裏存在下面兩個問題:

  • 如果想對教學樓整體上S鎖,首先需要確保教學樓中沒有正在維修的教室,如果有正在維修的教室,則需要等到維修結束才可以對教學樓整體上S鎖;
  • 如果想對教學樓整體上X鎖,首先需要確保教學樓中沒有上自習的教室以及正在維修的教室,如果有上自習的教室或者正在維修的教室,則需要等到上自習的所有同學都上完自習離開,以及維修工維修完教室離開後才可以對教學樓整體上X鎖。

我們在對教學樓整體上鎖(表鎖)時,怎麼知道教學樓中有沒有教室已經被上鎖(行鎖)了呢?一次檢查每一間教室門口有沒有上鎖?這效率也太慢了吧!遍歷是不可能遍歷的,這輩子都不可能遍歷的。於是設計InnoDB的大叔提出了一種稱為意向鎖(Intention Lock)的東西。

  • 意向共享鎖(Intention Shared Lock):簡稱IS鎖,當事務準備在某條記錄上加S鎖時,需要先在表級別加一個IS鎖。
  • 意向獨佔鎖(Intention Exclusive Lock):簡稱IX鎖,當事務準備在某條記錄上加X鎖時,需要先在表級別加一個IX鎖。

視角回到教學樓和教室上來:

  • 如果有學生到教室中上自習,那麼他現在整棟教學樓門口放一把IS鎖(表級鎖),然後再到教室門口放一把S鎖(行鎖);
  • 如果有維修工到教室進行維修,那麼他現在整棟教學樓門口放一把IX鎖(表級鎖),然後再到教室門口放一把X鎖(行鎖)。

之後:

  • 如果有上級領導要參觀教學樓,也就是想在教學樓門口前放S鎖(表鎖)時,首先要看一下教學樓門口有沒有IX鎖;如果有,則意味着有教室在維修,需要等到維修結束把IX鎖車掉後,才可以在整棟教學樓上加S鎖;
  • 如果有考試佔用教學樓,也就是想在教學樓門口前放X鎖(表鎖)時,首先要看一下教學樓門口有沒有IS鎖或IX鎖;如果有,則意味着有教室正在上自習或者在維修,需要等到學生們上完自習或者維修結束把IS鎖和IX鎖車掉後,才可以在整棟教學樓上加X鎖。
貼士:學生在教學樓門口加IS鎖時,是不關心教學樓門口是否有IX鎖的;維修工在教學樓加IX鎖時,是不關心教學樓門口是否有IS鎖或者其他IX鎖的。IS鎖和IX鎖只是用來判斷當前教學樓裏有沒有被佔用的教室,也就是隻有在對教學樓加S鎖或者X鎖後才會用到。

總結一下:IS鎖、IX鎖是表級鎖,它們的提出僅僅為了在之後加表級別的S鎖和X鎖時可以快速判斷表中的記錄是否被上鎖,以避免用遍歷的方式來查看錶中有沒有上鎖的記錄;也就是説其實IS鎖和IX鎖是兼容的,IX鎖和IX鎖是兼容的。
下面畫個表來看一下表級別的各種鎖的兼容性。

兼容性 X鎖 IX鎖 S鎖 IS鎖
X鎖 不兼容 不兼容 不兼容 不兼容
IX鎖 不兼容 兼容 不兼容 兼容
S鎖 不兼容 不兼容 兼容 兼容
IS鎖 不兼容 兼容 兼容 兼容

MySQL中的行鎖和表鎖

其他存儲引擎中的鎖

對於MyISAM、MEMORY、MERGE這些存儲引擎來説,它們只支持表級鎖,而且這些存儲引擎並不支持事務,所以當我們為使用這些存儲引擎的表加鎖時,一般都是針對當前會話來説的。

比如在Session 1中對一個表執行SELECT操作,就相當於為這個表加了一個表級別的S鎖。如果在SELECT操作未完成時,在Session 2中對這個表執行UPDATE操作,相當於要獲取表的X鎖,此操作將會被阻塞。直到Session 1中的SELECT操作完成,釋放掉表級別的S鎖後,在Session 2中對這個表執行UPDATE操作才能繼續獲取X鎖,然後再執行具體的更新語句。

貼士:因為使用MyISAM、MEMORY、MERGE這些存儲引擎的表在同一時刻只允許一個會話對錶進行寫操作,所以這些存儲引擎實際上最好用在只讀場景下,或者用在大部分都是讀操作或者單用户的場景下。

InnoDB存儲引擎中的鎖

InnoDB存儲引擎支持表級鎖,也支持行級鎖。表級鎖粒度粗,佔用資源較少。不過有時我們僅僅需要鎖住幾條記錄,如果使用表級鎖,效果上相當於為表中的所有記錄都加鎖,所以性能比較差,行級鎖粒度細,可以實現更精準的併發控制,但是佔用資源較多。

1. InnoDB中的表級鎖

  • 表級別的S鎖、X鎖

在對某個表執行SELECT、INSERT、DELETE、UPDATE語句時,InnoDB存儲引擎是不會為這個表添加表級別的S鎖或者X鎖的。
另外,在對某個表執行一些諸如ALTER TABLE、DROP TABLE的DDL語句時,其他事務在對這個表併發執行諸如SELECT、INSERT、DELETE、UPDATE等語句時,會發生阻塞。同理,某個事務在對某個表執行SELECT、INSERT、DELETE、UPDATE語句時,在其他會話中對這個表執行DDL語句也會發生阻塞。這個過程其實是通過在server層使用一種稱為元數據鎖(Metadata Lock,MDL)的東西來實現的,一般情況下也不會使用InnoDB存儲引擎自己提供的表級別的S鎖和X鎖。

其實,InnoDB存儲引擎的表級S鎖或者X鎖相當 “雞肋”,只會在一些特殊情況下(比如在系統崩潰恢復時)用到。不過我們還是可以手動獲取一下,比如在系統變量autocommit=0、innodb_table_kicjs=1時,要手動獲取InnoDB存儲引擎提供的表t的S鎖或者X鎖可以按照下面這樣來寫語句。

  • LOCK TABLES t READ:InnoDB存儲引擎會對錶t加表級別的S鎖。
  • LOCK TABLES t WRITE:InnoDB存儲引擎會對錶t加表級別的X鎖。
    不過清儘量避免在使用InnoDB存儲引擎的表上使用LOCK TABLES這樣的手動鎖表語句,它們並不會提供什麼額外的保護,只是會降低併發能力而已。InnoDB的厲害之處是實現了更細粒度的行級鎖,關於表級別的S鎖和X鎖大家瞭解一下就罷了。
  • 表級別的IS鎖、IX鎖
    當對使用InnoDB存儲引擎的表的某些記錄加S鎖之前,需要先在表級別加一個IS鎖;當對使用InnoDB存儲引擎的表的某些記錄加X鎖之前,需要先在表級別加一個IX鎖。IS鎖和IX鎖的使命只是為了後續在家表級別的S鎖和X鎖時,判斷表中是否有已經被加鎖的記錄,以避免用遍歷的方式來查看錶中有沒有上鎖的記錄。
  • 表級別的AUTO-INC鎖
    在使用MySQL的過程中,我們可以為表的某個列添加AUTO_INCREMENT屬性,之後在插入記錄時,可以不指定該列的值,系統會自動為它賦予遞增的值。比如我們創建一個表。

    CREATE TABLE t (
      id INT NOT NULL AUTO_INCREMENT,
      c VARCHAR(100),
      PRIMARY KEY(id)    
    ) Engine=InnoDB CHARSET=utf8;

    由於這個表的id字段聲明瞭AUTO_INCREMENT,也就意味着在書寫插入語句時不需要為其賦值。比如下面這樣:

    INSERT INTO t(c) VALUES('aa'),('bb');

    上面這條插入語句並沒有為id列顯式賦值,系統會自動為它賦予遞增的值,效果如下:
    image.png
    系統自動給AUTO_INCREMENT修飾的列進行遞增賦值的方式主要有下面兩個。

    • 採用AUTO-INC鎖,也就是在執行插入語句時就加一個表級別的AUTO-INC鎖,然後為每條待插入記錄的AUTO_INCREMENT修飾的列分配遞增的值。在該語句執行結束後,再把AUTO-INC鎖釋放掉。這樣一來,一個事務在持有AUTO-INC鎖的過程中,其他事務的插入語句都要被阻塞,從而保證一個語句中分配的遞增值是連續的

    比如我們的插入語句在執行前並不確定具體要插入多少條記錄(無法預計即將插入記錄的數量),比如使用INSERT ...SELECT、REPLACE ...SELECT或者LOAD DATA這種插入語句,一般是使用AUTO_INC鎖為AUTO_INCREMENT修飾的列生成對應的值。

    • 採用一個輕量級的鎖,在為插入語句生成AUTO_INCREMENT修飾的列的值時獲取這個輕量級鎖,然後在生成本次插入語句需要用的AUTO_INCREMENT修飾的列的值之後,就把該輕量級鎖釋放掉,而不需要等到整個插入語句執行完後才釋放鎖。
貼士:需要注意的是,這個AUTO-INC鎖的作用範圍只是單個插入語句,在插入語句執行完成後,這個鎖就被釋放了。這與之前介紹的鎖在事務結束時釋放是不一樣的。

如果我們的插入語句在執行前就可以確定具體要插入多少記錄,那麼一般採用輕量級鎖的方式對AUTO_INCREMENT修飾的列進行賦值。這種方式可以避免鎖定表,可以提升插入性能。

貼士:設計InnoDB的大叔提供了一個名為innodb_autoinc_lock_mode的系統變量,用來控制到底使用上述兩種方式的哪一種來為AUTO_INCREMENT修飾的列進行賦值,當innodb_autuinc_lock_mode的值為0時,一律採用AUTO-INC鎖;當innodb_autoinc_lock_mode的值為2時,一律採用輕量級鎖;當innodb_autoinc_lock_mode的值為1時,兩種方式混着來(也就是在插入記錄的數量確定時採用輕量級鎖,不確定時使用AUTO-INC鎖)。不過,當innodb_autoinc_lock_mode的值為2時,可能會造成不同事務中的插入語句為AUTO_INCREMENT修飾的列生成的值是交叉的,這在有主從複製的場景中是不安全的。

2.InnoDB中的行級鎖

行級鎖,也稱為記錄鎖,顧名思義就是在記錄上加的鎖。不過設計InnoDB的大叔很有才,一個行鎖玩出了多種 "花樣",也就是把行鎖分成了各種類型。換句話説,即使對同一條記錄加行鎖,如果記錄的類型不同,起到的功效也是不同的。
為了故事發展,有一張表。

Create Table: CREATE TABLE `hero` (
  `number` int NOT NULL,
  `name` varchar(100),
  `country` varchar(100),
  PRIMARY KEY (`number`)
) ENGINE=InnoDB CHARSET=utf8

我們主要是想用這個表存儲三國時期的英雄人物。向這個表中插入幾條記錄

image.png

貼士:utf8字符集沒有對漢字進行排序,所以在加了首字母

這裏把B+樹進行了超級簡化,只把聚簇索引葉子節點中的記錄給拿了出來,目的是想強調聚簇索引種對記錄是按照主鍵大小排序的。這裏還略掉了聚簇索引種的隱藏列,這裏明白即可。
下面看看都有哪些常用的行級鎖類型。

  • Record Lock
    前面提到的記錄鎖就是這種類型,也就是僅僅把一條記錄鎖上。我們暫時叫 "正經記錄鎖"。這種鎖類型官方名稱為LOCK_REC_NOT_GAP。比如我們為number值為8的那條記錄加一個正經記錄鎖。
    正經記錄鎖是有S鎖和X鎖之分的,我們分別稱S型正經記錄鎖和X型正經記錄鎖。當一個事務獲取了一條記錄的S型正經記錄鎖後,其他事務也可以繼續獲取該記錄的S型正經記錄鎖。當一個事務獲取了一條記錄的X型正經記錄鎖後,其他事務既不可以繼續獲取改記錄的S型正經記錄鎖,也不可以繼續獲取X型正經記錄鎖。
  • Gap Lock
    MySQL在REPEATABLE READ隔離級別下是可以在很大程度上解決幻讀現象的。解決方案有兩種:使用MVCC方案解決;使用加鎖方案解決。但是在使用加鎖方案解決時又個大問題,就是事務在第一次執行讀取操作時,那些幻影記錄上不存在,我們無法給這些幻影記錄加上正經記錄鎖。不過這難不倒設計InnoDB的大叔,它們提出了一種稱為Gap Lock的鎖。這種鎖類型的官方名稱為LOCK_GAP,也可以簡稱為gap鎖。比如我們為number值為8的那條記錄加一個gap鎖。

。。。

為number值為8的記錄加了gap鎖,這意味着不允許別的事務在number值為8的記錄的前面的縫隙插入新記錄,其實就是number列的值在區間(3,8)的新記錄是不允許立即插入的。比如有另一個事務想插入一條number為4的新記錄,首先要定位到該條記錄的下一條記錄,也就是number值為8的記錄,而這條記錄上又有一個gap鎖,所以就會阻塞插入操作;直到擁有這個gap鎖的事務提交了之後將該gap鎖釋放掉,其他事務才可以插入number列的值在區間(3,8)中的新記錄。

這個gap鎖的提出僅僅是為了防止插入幻影記錄而提出的。雖然gap鎖有共享gap鎖和獨佔gap鎖這樣的説法,但是他們起到的作用都是相同的。而且如果對一條記錄加了gap鎖(無論是共享還是獨佔的gap鎖),並不會限制其他事務對這條記錄加正經記錄鎖或者繼續加其他gap鎖。在強調一遍,gap鎖的作用僅僅是為了防止插入幻影記錄而已。

不知道大家是否發現了一個問題:給一條記錄加gap鎖只是不允許其他事務像這條記錄前面的間隙插入新記錄;那對於最後一條記錄之後的間隙,也就是hero表中number值為20的記錄之後的間隙該咋辦呢?也就是説,給哪條記錄加gap鎖才能阻止其他事務插入number值在區間(20,+)的新記錄呢?這時候應該想起數據頁中那兩條偽記錄了。

  • Infimum記錄:表示該頁面中最小的記錄。
  • Supremum記錄:表示該頁面中最大的記錄。

為了阻止其他事務插入number值在區間(20,+)的新記錄,我們可以給索引中最後一條記錄(也就是number值為20的那條記錄)所在頁面的supremum記錄上加一個gap鎖。

這樣就可以阻止其他事務插入number值在區間(20,+)的新記錄。

  • Next-Key Lock
    正經記錄鎖和一個gap鎖的合體,既能保護這條記錄,又能阻止別的事務將新記錄插入到被保護記錄前面的間隙中。
  • Insert Intention Lock
    插入意向鎖
user avatar xyjzfx 頭像 ljc1212 頭像 fanudekaixinguo 頭像 youyudeshangpu_cny857 頭像 feichangkudechongfengyi 頭像 seatunnel 頭像 liujiaxiaobao 頭像 zhaoqianglaoshi 頭像 240cgxo4 頭像 mengxiang_592395ab95632 頭像 danieldx 頭像 shuirongshui 頭像
點贊 18 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.