天天看點

鎖的分類(四)

  • ​頁級别:記錄鎖​

記錄鎖也就是僅僅把一條記錄鎖上,官方的類型名稱為: LOCK_REC_NOT_GAP 。比如我們把id值為8的那條記錄加一個記錄鎖的示意圖如圖所示。
僅僅是鎖住了id值為8的記錄,對周圍的資料沒有影響。      
鎖的分類(四)
  • 結論
記錄鎖是有S鎖和X鎖之分的,稱之為 S型記錄鎖 和 X型記錄鎖 。
當一個事務擷取了一條記錄的S型記錄鎖後,其他事務也可以繼續擷取該記錄的S型記錄鎖,但不可以繼續擷取X型記錄鎖;
當一個事務擷取了一條記錄的X型記錄鎖後,其他事務既不可以繼續擷取該記錄的S型記錄鎖,也不可以繼續擷取X型記錄鎖      
  • 案例
# 案例1
# 開啟第1個連接配接
# 開啟1個事務
begin;
# 相當于為這1行開啟1個排他鎖
update student set name = "chen" where id = 1;

# 開啟第2個連接配接
# 開啟第2個事務
begin;
# 對其他行開啟共享鎖,開啟成功
select * from student where id = 3 lock in share mode;
# 對第1個連接配接中的那一行開啟共享鎖,開啟失敗,因為不相容
select * from student where id = 1 lock in share mode;

# 對其他行開啟排他鎖,開啟成功
update student set name = "li" where id = 3;

# 對同一行開啟排他鎖,開啟失敗
update student set name = "xing" where id = 1;

# 當第1個連接配接中釋放排他鎖,關閉事務後
commit;

# 第2個連接配接中對這1行開啟排他鎖,開啟成功
update student set name = "xing" where id = 1;      
  • ​頁級鎖:間隙鎖(Gap Locks)​

    ​:指定2行中間不允許插入資料
MySQL 在 REPEATABLE READ 隔離級别下是可以解決幻讀問題的,解決方案有兩種,可以使用 MVCC 方案解決,也可以采用 加鎖 方案解決。但是在使用加鎖方案解決時有個大問題,
就是事務在第一次執行讀取操作時,那些幻影記錄尚不存在,我們無法給這些 幻影記錄 加上 記錄鎖 。InnoDB提出了一種稱之為Gap Locks 的鎖,官方的類型名稱為: LOCK_GAP       
  • 案例
# 案例1
# 開啟1個新的連接配接,開啟1個事務
begin;
# 為某個不存在的行添加1個共享鎖
select * from student where id= 5 lock in share mode;

# 開啟第2個新的連接配接,開啟1個事務
begin;
# 為同一行添加1個排他鎖,按道理說不能添加成功,針對同一行記錄添加共享鎖和排他鎖是不相容的
# 但如果這一行記錄是不存在的,則可以同時添加共享鎖和排他鎖
# 這一行不存在的記錄存在與某2行記錄的中間,這時添加的這個鎖也成為間隙鎖
# 添加1個排他鎖,添加成功
select * from student where id = 5 for update;

# 在間隙鎖還未釋放之前,向這個間隙插入資料,會插入失敗,會處于阻塞狀态,這就是間隙鎖的作用
insert into student(id, name, class) value(6, "gou", "man");

# 案例2
# 開啟1個新的連接配接,開啟1個事務
begin;
# 由于最大的數是20,如果在20到無窮大的這個區間開啟1個間隙鎖,則20到無窮大這個區間将被間隙鎖鎖定,不允許插入資料
select * from student where id = 25 lock in share mode;

# 開啟第2個連接配接,開啟1個事務
begin;
# 向20到無窮大的區間插入資料,插入失敗
insert into student(id, name, class) value(21, "gou", "man");
insert into student(id, name, class) value(26, "gou", "man");

# 向20之前的區間插入資料,插入成功
insert into student(id, name, class) value(17, "gou", "man");      
  • ​間隙鎖導緻死鎖​

# 開啟第1個新的連接配接,開啟1個事務
begin;
# 開啟1個間隙鎖
select * from student where id = 5 lock in share mode;

# 開啟第2個連接配接,開啟1個新的事務
begin;
# 開啟1個相同的間隙鎖
select * from student where id = 5 for update;

# 第2個連接配接中向間隙中插入1條記錄,會處于阻塞狀态
insert into student(id, name, class) value(7, "aaa", "giao");

# 切換到第1個連接配接的頁面,向間隙中再次插入1條記錄,這時會直接報死鎖的錯誤
insert into student(id, name, class) value(6, "bbb", "giao");

# 死鎖的原因:第1個間隙鎖不允許第2個連接配接中的插入操作執行,第2個間隙鎖不允許第1個連接配接中的插入操作執行
# 要想第1個連接配接中的插入操作執行,第2個連接配接釋放鎖,但第2個連接配接中有1條插入語句處于阻塞狀态,不能結束
# 要想第2個連接配接中的插入操作執行,第1個連接配接釋放鎖,但第1個連接配接中有1條插入語句處于阻塞狀态,不能結束
# 最後第1個連接配接中的插入語句報死鎖錯誤,第2個連接配接中的插入語句則插入成功了

# 第2個連接配接中插入語句執行成功的原因:發生死鎖時,mysql的處理死鎖的1中政策是将某1個處于僵持狀态的事務進行復原,選擇成本更低的1個事務執行
# 是以這裡復原了第1個連接配接中的事務,第2個連接配接沒有阻塞則執行成功了      

繼續閱讀