鎖是數據庫區別與文件系統的一個關鍵特性,鎖機制用於管理對共享資源的併發訪問。
InnoDB使用的鎖類型,分別有:html
InnoDB實現了兩種標準的行級鎖:共享鎖(S)和排他鎖(X)java
共享鎖:容許持有該鎖的事務讀取行記錄。若是事務 T1 擁有記錄 r 的 S 鎖,事務 T2 對記錄 r 加鎖請求:若想要加 S 鎖,能立刻得到;若想要得到 X 鎖,則請求會阻塞。mysql
排他鎖:容許持有該鎖的事務更新或刪除行記錄。若是事務 T1 擁有記錄 r 的 X 鎖,事務 T2 對記錄 r 加鎖請求:不管想獲取 r 的 S 鎖或 X 鎖都會被阻塞。算法
S 鎖和 X 鎖都是行級鎖。sql
InnoDB 支持多粒度的鎖,容許一行記錄同時持有兼容的行鎖和表鎖。意向鎖是表級鎖,代表一個事務以後要獲取表中某些行的 S 鎖或 X 鎖。數據庫
InnoDB中使用了兩種意向鎖併發
例如:性能
SELECT ... LOCK IN SHARE MODE
,設置了 IS 鎖SELECT ... FOR UPDATE
,設置了 IX 鎖意向鎖協議以下所示:大數據
這些規則能夠總結爲下面的圖表(橫向表示一個事務已經獲取了對應的鎖,縱向表示另一個事務想要獲取對應的鎖):優化
IX,IS是表級鎖,不會和行級的X,S鎖發生衝突。只會和表級的X,S發生衝突
X | IX | S | IS | |
---|---|---|---|---|
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
IX | 不兼容 | 兼容 | 不兼容 | 兼容 |
S | 不兼容 | 不兼容 | 兼容 | 兼容 |
IS | 不兼容 | 兼容 | 兼容 | 兼容 |
當請求的鎖與已持有的鎖兼容時,則加鎖成功;若是衝突的話,事務將會等待已有的衝突的鎖釋放
IX 和 IS 鎖的主要目的是代表:某個請求正在或者將要鎖定一行記錄。意向鎖的做用:意向鎖是在添加行鎖以前添加。當再向一個表添加表級 X 鎖的時候
意向鎖使用 SHOW ENGINE INNODB STATUS
查看當前鎖請求的信息:
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
InnoDB中,對每一個含有自增加值的表都有一個自增加計數器(aito-increment counter)。當對含有自增加計數器的表進行插入操做時,這個計數器會被初始化。執行以下語句會得到自增加的值
SELECT MAX(auto_inc_col) FROM t FOR UPDATE;
插入操做會依據這個自增加的計數器值加1賦予到自增加列。這種實現方式是AUTO_INC Locking。這種鎖採用了一種特殊的表鎖機制,爲提升插入的性能,鎖不是在一個事務完成後釋放,而是在完成對自增加值插入的SQL語句後當即釋放。雖然AUTO-INC Locking必定方式提高了併發插入的效率,但仍是存在性能上的一些問題:
InnoDB提供了一種輕量級互斥量的自增加實現機制,大大提升了自增加值插入的性能。提供參數innodb_autoinc_lock_mode來控制自增加鎖使用的算法,默認值爲1。他容許你在可預測的自增加值和最大化併發插入操做之間進行權衡。
插入類型的分類:
插入類型 | 說明 |
---|---|
insert-like | 指全部的插入語句,例如:insert、replace、insert ... select、replace... select、load data |
simple inserts | 指再插入前就肯定插入行數的語句。例如:insert、replace等。注意:simple inserts不包含 insert ... on duplicate key update 這類sql語句 |
bulk inserts | 指在插入前不能肯定獲得插入行數的語句,例如:insert ... select、 replace ... select、load data |
mixed-mode inserts | 指插入中有一部分的值是自增加的,一部分是肯定的。例如:insert into t1(c1, c2) values (1, 'a'), (NULL, 'b'), (5, 'c'), (NULL,'d'); 也能夠指 insert ... on duplicate key update 這類sql語句 |
innodb_autoinc_lock_mode 在不一樣設置下對自增加的影響:
MySQL 5.1.22版本以前自增加的實現方式,經過表鎖的AUTO-INC Locking方式
對於『simple inserts』,該值會用互斥量(mutex)對內存中的計數器進行累加操做。對於『bulk inserts』會用傳統的AUTO-INC Locking方式。這種配置下,若是不考慮回滾,自增加列的增加仍是連續的。須要注意的是:若是已經使用AUTO-INC Locking方式去產生自增加的值,而此時須要『simple inserts』操做時,還須要等待AUTO-INC Locking的釋放
對於全部『insert-like』自增加的產生都是經過互斥量,而不是AUTO-INC Locking方式。這是性能最高的方式。但會帶來一些問題:
所以,使用這種方式,任何狀況下都須要使用row-base replication,這樣才能保證最大併發性能和replication的主從數據的一致 |
InnoDB存儲引擎行鎖的算法
行鎖是加在索引記錄上的鎖,例如:SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE
,會阻止其餘事務插入、更新或刪除 t.c1 = 10 的記錄
行鎖老是在索引記錄上面加鎖,即便一張表沒有設置任何索引,InnoDB會建立一個隱藏的聚簇索引,而後在這個索引上加上行鎖。
行鎖使用 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 ;;
間隙鎖是加在索引記錄間隙之間的鎖,或者在第一條索引記錄以前、最後一條索引記錄以後的區間上加的鎖。例如:SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
這條語句阻止其餘的事務插入一條 t.c1 = 15 的記錄,由於在10-20的範圍值都已經被加上了鎖。
間隙鎖只在RR隔離級別中使用。若是一條sql使用了惟一索引(包括主鍵索引),那麼不會使用到間隙鎖
例如:id 列是惟一索引,下面的語句只會在 id = 100 行上面使用Record Lock,而不會關心別的事務是否在上述的間隙中插入數據。若是 id 列沒有索引或者不是惟一索引,這個語句會在上述的間隙上加鎖。
SELECT * FROM child WHERE id = 100 FOR UPDATE;
Next-Key Lock是結合了Gap Lock 和 Record Lock的一種鎖算法。
當掃描表的索引時,InnoDB以這種形式實現行級的鎖:遇到匹配的的索引記錄,在上面加上對應的 S 鎖或 X 鎖。所以,行級鎖其實是索引記錄鎖。若是一個事務擁有索引上記錄 r 的一個 S 鎖或 X 鎖,另外的事務沒法當即在 r 記錄索引順序以前的間隙上插入一條新的記錄。
假設有一個索引包含值:10,11,13和20。下列的間隔上均可能加上一個Next-Key 鎖(左開右閉)
(negative infinity, 10] (10, 11] (11, 13] (13, 20] (20, positive infinity)
在最後一個區間中,Next-Key鎖 鎖定了索引中的最大值到 正無窮。
默認狀況下,InnoDB啓用 RR 事務隔離級別。此時,InnoDB在查找和掃描索引時會使用 Next-Key 鎖,其設計的目的是爲了解決『幻讀』的出現。
當查詢的列是惟一索引狀況下,InnoDB會對Next-Key Lock進行優化,降級爲Record Lock,即只鎖住索引自己,而不是範圍。
next-key 鎖 使用 SHOW ENGINE INNODB STATUS
輸出以下:
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 ;;
插入意向鎖是一種在數據行插入前設置的gap鎖。這種鎖用於在多事務插入同一索引間隙時,若是這些事務不是往這段gap的同一位置插入數據,那麼就不用互相等待。假若有4和7兩個索引記錄值。不一樣的事務嘗試插入5和6的值。在不一樣事務獲取分別的 X 鎖以前,他們都得到了4到7範圍的插入意向鎖,可是他們無需互相等待,由於5和6這兩行不衝突。
例如:客戶端A和B,在插入記錄獲取互斥鎖以前,事務正在獲取插入意向鎖。
客戶端A建立了一個表,包含90和102兩條索引記錄,而後去設置一個互斥鎖在大於100的全部索引記錄上。這個互斥鎖包含了在102記錄前的gap鎖。
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 開啓一個事務在這段gap上插入新紀錄,這個事務在等待獲取互斥鎖以前,獲取了一把插入意向鎖。
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 ;;...
給定兩個SQL來分析InnoDB下加鎖的過程:
SQL1:select * from t1 where id = 10; SQL2:delete * from t1 where id = 10;
事務隔離級別爲默認隔離級別Repeatable Read。而對於id不一樣的索引類型,會有不一樣的結論。(總結自何登成大神的 MySQL 加鎖處理分析)
SQL1:在RC和RR下,由於MVCC併發控制,select操做不須要加鎖,採用快照讀。讀取記錄的可見版本(多是歷史版本)
針對SQL2:以下分不一樣狀況
將主鍵上,id=10的記錄加上 X 鎖
id不是主鍵,而是一個惟一的二級索引,主鍵是name列。加鎖步驟以下:
聚簇索引加鎖的緣由:若是併發的一個SQL是經過主鍵索引來更新:update t1 set id = 100 where name = 'd';
此時,若是delete語句沒有將主鍵索引上的記錄加鎖,那麼併發的update就會感知不到delete語句的存在。違背同一條記錄的更新/刪除須要串行執行的約束。
加鎖步驟以下:
幻讀解決:
這幅圖中多了個GAP鎖,並非加到記錄上的,而是加在兩個記錄之間的位置。GAP 鎖就是 RR 隔離級別相對於 RC 隔離級別,不會出現幻讀的關鍵。GAP鎖保證兩次當前讀以前,其餘的事務不會插入新的知足條件的記錄並提交。
所謂幻讀,就是同一個事務,連續作兩次當前讀 (例如:select * from t1 where id = 10 for update;
),那麼這兩次當前讀返回的是徹底相同的記錄 (記錄數量一致,記錄自己也一致),第二次的當前讀,不會比第一次返回更多的記錄 (幻象)。
如圖中所示:考慮到B+樹索引的有序性,有哪些位置能夠插入新的知足條件的項 (id = 10):
所以,不只將知足條件的記錄鎖上 (X鎖),同時還經過GAP鎖,將可能插入知足條件記錄的3個GAP給鎖上,保證後續的Insert不能插入新的id=10的記錄,也就杜絕了同一事務的第二次當前讀,出現幻象的狀況。
當id是惟一索引時,則不須要加GAP鎖。由於惟一索引可以保證惟一性,對於where id = 10 的查詢,最多隻能返回一條記錄,並且新的 id= 10 的記錄,必定不會插入進來。
當id無索引時,只能進行全表掃描,加鎖步驟:
若是表中有上千萬條記錄,這種狀況是很恐怖的。這個狀況下,MySQL也作了一些優化,就是所謂的semi-consistent read。semi-consistent read開啓的狀況下,對於不知足查詢條件的記錄,MySQL會提早放鎖。針對上面的這個用例,就是除了記錄[d,10],[g,10]以外,全部的記錄鎖都會被釋放,同時不加GAP鎖
死鎖避免的一些辦法: