官方文檔:
命令查詢記錄
查詢事務的鎖定(可以查看事務存在的意圖鎖,記錄鎖)
show engine innodb status
查詢當前會話的事務級別
select @@transaction_isolation;
查詢全局事務級別
select @@gloabal.transaction_isolation;
設置當前會話的事務級別
set session transaction isolation level repeatable read;
鎖定讀取(只有在禁用自動提交時才能set autocommit=0)
設置共享模式鎖定(給select加讀鎖)
SELECT ... LOCK IN SHARE MODE
改要等待本事務提交。如果其他事務已經修改了要讀取的行但是沒有提交,則本事務等待其他事務結束,並獲取最新的值
查詢索引鎖定(給select加寫鎖)
SELECT ... FOR UPDATE
搜索到的索引記錄,鎖定行/索引條目,阻止其他事務更新這些行,設置共享模式鎖定,或者讀取行
Shared and Exclusive Locks 共享鎖和排他鎖
事務T1在row行上有共享鎖
1、事務T2申請共享鎖可立即獲得,T1, T2同時擁有共享鎖
2、事務T2申請排他鎖需要等待T1釋放
事務T1在row行上有排他鎖
1、事務T2申請共享鎖 or 排他鎖,均需等待T1釋放
Intention Locks 意圖鎖(表級鎖)
innodb支持多粒度鎖:即支持同時存在行鎖和標鎖。為了支持這個機制,有了意圖鎖
目的:表名事務之后需要在一給表的某行中需要共享 or 排他鎖
意圖共享鎖IS:事務打算在一張表的個別行申請共享鎖(或正擁有)(select ... for share)
意圖排他鎖IX:事務打算再一張表的個別行申請排他鎖(或正擁有)(select ... for update)
-
事務需要申請共享行鎖之前,先要申請表的意圖共享鎖或意圖排他鎖
-
是無需要申請排他航所之前,先要申請表的意圖排他鎖
X | IX | S | IS | |
---|---|---|---|---|
X | 互斥 | 互斥 | 互斥 | 互斥 |
IX | 互斥 | 兼容 | 互斥 | 兼容 |
S | 互斥 | 互斥 | 兼容 | 兼容 |
IS | 互斥 | 兼容 | 兼容 | 兼容 |
(意圖鎖之間互相兼容,其余遵循X,S互斥原則)
意圖鎖不會阻止除了完整表請求之外的任何(如何lock tables ... write)
某個事務上的某個意圖鎖SHOW ENGINE INNODB STATUS:
TABLE LOCK table test.t trx id 10080 lock mode IX
Record Locks 索引記錄鎖
SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE
給c1=10的行上排他鎖,如果查詢用的是聚集索引,就會使用此排他記錄鎖
SHOW ENGINE INNODB STATUS
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
Gap Locks 間隙鎖
SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
給10~20之間的行上排他間隙鎖,阻止其他事務更新
間隙鎖在部分隔離級別下使用
在唯一索引上的查詢(不包括多個col的唯一索引,搜索條件為某些列),不使用間隙鎖。例:SELECT * FROM child WHERE id = 100;
只有記錄鎖,沒有間隙鎖。如果id沒有索引或者不是唯一索引,會在100之前的所有行上間隙鎖
注意:不同的事務可以在一個間隙上擁有不同的間隙鎖。例如:事務A在一個間隙上擁有共享間隙鎖,同時事務B在同一間隙上擁有排他間隙鎖(無論是間隙S鎖還是間隙X鎖)。這個機制被允許存在是因為,一個記錄被刪除時,會和並不同事務在這個記錄上的間隙鎖
間隙鎖唯一的作用就是阻止其他事務在間隙上的更改,一個事務在一個記錄上有間隙鎖不阻止其他事務在此間隙上獲得間隙鎖
在讀提交隔離級別上,間隙鎖不被使用,除非在進行外鍵約束檢查和重復值檢查時。在讀提交隔離級別上,找不到行時,記錄鎖被釋放。
Next-Key Locks
這個鎖是索引數據上的記錄鎖和索引數據之前的間隙鎖的結合
InnoDB以這樣的方式執行行級鎖定:當它搜索或掃描表索引時,它會在遇到的索引記錄上設置共享鎖或排它鎖。因此,行級鎖實際上是索引記錄鎖。
在索引記錄上的next-key lock依然影響在索引記錄之前的間隙鎖。所以,next-key lock是一個索引記錄鎖+在這個索引數據之前的間隙鎖。如果一個會話有記錄R索引上的共享/排他鎖,其他會話不可以插入一個新的索引記錄在這個R索引之前
假設10,11,13,20上有索引,可能的next-key lock如下,最后一個間隔,next-key lock鎖定最大索引值之后的間隙
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
innodb在可重復度級別下,使用next-key lock用於查找和索引掃描,防止幻讀
查看鎖狀態
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
插入意圖鎖
插入意圖鎖在執行插入行數據之前的設置的一種間隙鎖
這個鎖表示,在想要插入的時候,多個事務插入共同的索引間隙不需要相互等待,若果他們的行在間隙中位置不同
假設,47上有索引記錄鎖,獨立的事務想要插入5和6,每個事務在得到插入行上的排他鎖之前,使用插入意圖鎖鎖住47這個間隙。但是每個事務不阻塞彼此
舉例:客戶端A創建一張表有2個索引數據(90和102),開始一個事務,放置一個排他索引記錄鎖在ID>100的數據,排他鎖也包括一個102數據之前的間隙鎖
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);
mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id |
+-----+
| 102 |
+-----+
客戶端B開始一個事務插入一條記錄到間隙中,這個事務在等待排他鎖的時候持有一個插入意圖鎖
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);
show engine innodb status查看
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000066; asc f;;
1: len 6; hex 000000002215; asc " ;;
2: len 7; hex 9000000172011c; asc r ;;...
AUTO-INC鎖定
一個AUTO-INC鎖是一個特殊的表級鎖,當事務插入表並有自增列的時候持有。舉例,如果一個事務在插入數據,其他事務需要等待自己的插入,第一個事務將得到連續的主鍵值
例子
客戶端A創建一個包含兩個索引記錄(90和102)的表,然后啟動一個事務,該事務對ID大於100的索引記錄放置獨占鎖。獨占鎖包括記錄102之前的間隙鎖:
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);
mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;