1. 程式人生 > 實用技巧 >MySQL-InnoDB行鎖

MySQL-InnoDB行鎖

InnoDB的鎖型別

InnoDB儲存引擎支援行鎖,鎖型別有兩種:

  • 共享鎖(S鎖)
  • 排他鎖(X鎖)

S和S不互斥,其他均互斥。

除了這兩種鎖以外,innodb還支援一種鎖,叫做意向鎖

那麼什麼是意向鎖?為什麼需要意向鎖呢?

考慮這種情況:
SessionA:已經持有表t某一行的X鎖,需要對行進行更新操作
SessionB:想申請表t的表鎖寫鎖

在沒有意向鎖之前,SessionA已經持有了行X鎖以後,如果SessionB也成功,意味著SessionB可以對這個表中的所有資料進行更新操作,與SessionA的行鎖是衝突的。

所以資料庫在同意SessionB的申請之前,會做以下的判斷:

  1. 檢查是否有其他的表級寫鎖存在
  2. 判斷表中每一行是否有行級X鎖存在

步驟2的判斷本身效率不高。所以產生了意向鎖,在申請行級鎖之前,先申請意向鎖,成功之後才能申請行鎖。(簡單理解為多儲存一個變數,便於申請表鎖的時候進行快速判斷)。

意向鎖分為兩種:

  • 意向共享鎖(IS鎖)
  • 意向排他鎖(iX鎖)

IS鎖和IX鎖之間彼此都不互斥,IS和IX只和表級別的讀寫鎖互斥。其中:

  • IX和表X和表S互斥
  • IS和表X互斥
一致性鎖定讀和一致性非鎖定讀
一致性非鎖定讀

當隔離型別設定為讀已提交和可重複讀時,我們寫的普通的select語句,不會申請鎖,也不會被阻塞,會從MVCC選擇一個檢視讀取資料。
兩種隔離狀態的區別在於:

  • 讀已提交永遠拿到的是最新的檢視
  • 可重複讀永遠使用事務剛建立時拿到的檢視
一致性鎖定讀

當寫這兩種select語句時,會進行加鎖操作。

  • select ... lock in share mode
    • 這個語句表示加入一個S鎖,其他事務也可以加S鎖
    • 使用場景:不同表之間的相同資料保持一致性時使用(存疑,暫時放著)
  • select ... for update
    • 這個語句表示加入一個X鎖,其他事務不能讀也不能寫
    • 使用場景:先讀,根據讀結果進行修改的操作時可以使用
行鎖的三種演算法(判斷鎖定的範圍)

InnoDB儲存引擎有三種行鎖的鎖定演算法:

  • record lock:鎖定單行
  • gap lock:鎖定範圍,不包含記錄本身
  • next-key lock:gap lock + record lock 既鎖定範圍又鎖定記錄本身

當select操作需要加鎖時,會按照上面的next-key lock進行加鎖。

  • 當查詢條件中有索引,並且是唯一索引時,可以只鎖定單條記錄,由next-key lock降級為 record lock。
  • 當查詢條件中有索引,並且索引是輔助索引時,不僅鎖住記錄本身,還會鎖定記錄前一個索引鍵值範圍(不包括區間頭部的值),除此之外,還會鎖定記錄值到下一個索引鍵值的範圍(不包括區間尾端的值);同時還會在主鍵索引的對應行上加record lock
實驗驗證

輸入下列建表語句:

create table z(a int,b int,primary key(a),key(b));
insert into z select 1,1;
insert into z select 3,1;
insert into z select 5,3;
insert into z select 7,6;
insert into z select 10,8;

在sessionA中,輸入下面查詢語句

select * from z where b=3 for update;

在sessionB中,輸入下面查詢語句

select * from z where b=1 for update;
select * from z where b=3 for update;
insert into z (a,b) values(4,2);
select * from z where b=6 for update;

其中,第一條和第四條可以正常返回,第二條第三條語句會阻塞,因為SessionA一直沒有commit,所以這兩條語句阻塞一段時間後會報鎖等待超時異常。

mysql> select * from z where b=1 for update;
+---+------+
| a | b    |
+---+------+
| 1 |    1 |
| 3 |    1 |
+---+------+
2 rows in set (0.00 sec)

mysql> select * from z where b=6 for update;
+---+------+
| a | b    |
+---+------+
| 7 |    6 |
+---+------+
1 row in set (0.00 sec)

mysql> select * from z where b=3 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into z (a,b) values(4,2);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

結果分析
查詢條件為b=3,所以會使用b列的輔助索引來查詢,找到記錄3後

  • 使用record lock鎖定主鍵索引a=5的記錄
  • 使用next-key lock鎖定輔助索引(1,3)3 (3,6)這個範圍的記錄

在SessionA提交事務之前,這個範圍內的記錄都加的是X排他鎖,所以第二條和第三條記錄都需要阻塞等待。