InnoDB中不同SQL語句設置的鎖

鎖定讀,update,或delete一般會在SQL語句處理過程中掃描的每個索引記錄上加記錄鎖。其並不關心排除該數據行的語句中是否有where條件。InnoDB並不記得準確的where條件,但僅知道被掃描的索引範圍。
鎖定通常爲下一鍵值鎖,其將阻塞往鎖定記錄前的間隙插入數據的操作。然而,間隙鎖能被顯式關閉,從而引起下一鍵值鎖不被使用。
如果二級索引被用於查找且索引記錄鎖被設置爲排他鎖,InnoDB也會獲取相應的簇索引記錄並在其上加鎖。
如果SQL語句沒有合適的索引可用,mysql必須掃描整張表來處理該語句,那麼該表的每個數據行將被鎖定,其反過來阻塞其他會話的所有對該表的插入操作。創建好的索引非常重要,這樣,查詢不必掃描很多數據行。
InnoDB設置如下特定類型的鎖。
1)SELECT ... FROM爲一致性讀,其讀取數據庫快照並不加鎖,除非事務隔離級別設置爲serializable。對serializable級別,查找過程將對遇到的索引記錄加共享下一鍵值鎖。然而,對於通過唯一索引查找唯一數據行的語句,僅需加一個索引記錄鎖。
2)SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE,會對掃描的數據行加鎖,且會釋放不符合結果集條件的數據行的鎖(例如:如果數據行並不符合where子句設定的標準)。然而,某些場景下,因爲查詢執行期間結果集和行源間的關係丟失,導致數據行鎖也許並不會被立即釋放。例如:在UNION中,掃描(且被鎖定)的表數據行被評估是否符合結果集標準前,也許被插入一張臨時表。這種場景下,臨時表中數據行與原表中數據行的關係丟失,因此,直到查詢結束,原表中的數據行鎖不會被釋放。
3)SELECT ... FOR UPDATE會對查找遇到的每個記錄加下一鍵值鎖。然而,通過唯一索引查找唯一數據行的語句只要求加一個索引記錄鎖。
對查找遇到的索引記錄,SELECT ... FOR UPDATE會阻塞其他會話運行LECT ... LOCK IN SHARE MODE或特定事務級別讀數據。一致性讀會忽略加在讀視圖中記錄上的任何鎖。
4)UPDATE ... WHERE ...會對查找遇到的每個記錄加排他下一鍵值鎖。然而,通過唯一索引查找唯一數據行的語句只需一個索引記錄鎖。
5)當UPDATE修改簇索引記錄時,受影響的二級索引記錄也會被加隱式鎖。UPDATE執行插入新二級索引記錄前的重複數據檢查掃描和插入新的二級索引記錄時,將也會對受影響的二級索引記錄加共享鎖。
6)DELETE FROM ... WHERE ...會對查找遇到的每個記錄加排他下一鍵值鎖。然而,通過唯一索引查找唯一數據行的語句只需一個索引記錄鎖。
7)INSERT會對插入的數據行加排他鎖。這些鎖爲索引記錄鎖,而非下一鍵值鎖(也就是,沒有間隙鎖),且不會阻止其他會話往插入數據行前的間隙插入數據。
插入數據行前,一種叫做插入意向間隙鎖(insert intention gap lock)的間隙鎖被加。該鎖表示往同一索引間隙但不同位置插入數據的多個事務無需相互等待。假設有值爲4和7的索引記錄。分別想插入值爲5和6數據行的單獨事務,在得到插入行的排他鎖前,都將用插入意向鎖鎖定4和7間的間隙,因爲數據行不衝突,所以事務間不會互相阻塞。
如果重複鍵值錯誤發生,將在重複索引記錄上加共享鎖。當有多個會話試圖插入相同數據行但一個會話已經持有排他鎖時,這種共享鎖的使用能導致死鎖。當另一個會話刪除了該數據行時這種死鎖將會發生。假設InnoDB表t1有下述結構:
CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
現在假設三個會話依次執行下述操作:
Session 1:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
Session 2:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
Session 3:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
Session 1:
ROLLBACK;
會話1的第一個操作獲取了該行的排他鎖。會話2和會話3的操作導致重複鍵值錯誤,它們都將申請該數據行上的一個共享鎖。當會話1回滾時,它釋放了該數據行上的排他鎖,而正在排隊的會話2和會話3被授予共享鎖。
此時,會話2和會話3發生死鎖:因爲兩個會話都持有該數據行上的共享鎖,因此,兩個會話都不能獲取該數據行的排他鎖。
如果該表已經包含鍵值1的數據行,而三個會話依次執行下述操作,類似的情形也會發生:
Session 1:
START TRANSACTION;
DELETE FROM t1 WHERE i = 1;
Session 2:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
Session 3:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
Session 1:
COMMIT;
會話1的第一個操作獲取改行的一個排他鎖。會話2和會話3都因重複鍵值而報錯,它們都將申請該數據行的一個共享鎖。當會話1提交時,它釋放了該數據行上的排他鎖,正在排隊的會話2和會話3被授予共享鎖。此時,會話2和會話3會發生死鎖:因爲兩個會話都持有共享鎖,所以,都不能獲取該數據行上的排他鎖。
8)INSERT ... ON DUPLICATE KEY UPDATE與簡單的INSERT不同,當重複鍵錯誤發生時,該語句將在特定數據行上加一個排他鎖而不是共享鎖。重複主鍵值發生時會加一個排他索引記錄鎖。重複唯一鍵值發生時會加一個排他下一鍵值鎖。
9)如果唯一鍵上沒有衝突,REPLACE像INSERT一樣被處理。否則,替換數據行上將加一個排他下一鍵值鎖。
10)INSERT INTO T SELECT ... FROM S WHERE ...在每個插入到T表中的數據行上加一個排他索引記錄鎖(不加間隙鎖)。如果事務隔離級別爲read committed,或innodb_locks_unsafe_for_binlog被開啓且事務隔離級別不爲serializable,InnoDB將在S上做一致性讀(沒有鎖)。否則,InnoDB將爲S表中的數據行加共享下一鍵值鎖。InnoDB必須對後一種場景加鎖:在基於語句二進制日誌的前滾恢復期間,每個SQL語句必須像最初那樣被執行。
CREATE TABLE ... SELEC ...通過共享下一鍵值鎖或作爲一致性讀運行,就像INSERT ... SELECT那樣。
當SELECT用於REPLACE INTO t SELECT ... FROM s WHERE ...或UPDATE t ... WHERE col IN(SELECT ... FROM s ...)結構時,InnoDB爲s表中的數據行加共享下一鍵值鎖。
11)當初始化一個表的先前確定的AUTO_INCREMENT列時,InnoDB會在該AUTO_INCREMENT列相關索引的末端加一個排他鎖。訪問自增計數器時,InnoDB用一個確定的自增表鎖模式,該模式下鎖將僅持續到當前SQL語句結束,而非整個事務結束。當持有該自增表鎖時,其他會話不能向該表中插入數據。InnoDB獲取之前初始化的AUTO_INCREMENT列值時無需加任何鎖。
12)如果表上定義一個外鍵約束,任何需要檢查約束條件的insert,update或delete都將在查看檢查該約束的記錄上加共享記錄級鎖。當約束失敗時,InnoDB也會加這些鎖。
13)LOCK TABLES加表鎖,但由InnoDB之上的較高mysql層加這些鎖。如果innodb_table_locks=1(默認)和autocommit=0,InnoDB會知道表鎖,而InnoDB之上的mysql層會知道行鎖的情況。
否則,當涉及這些表鎖時,InnoDB自動死鎖探測機制並不能探測到死鎖。同時,因爲這種情況下較高mysql層並不清楚行鎖情況,有可能在另一個會話目前持有行鎖的表上獲得一個表鎖。然而,這並不能危機事務的完整性。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章