一、簡述
數據庫的鎖是在多線程高并發的情況下用來保證數據穩定性和一致性的一種機制。MySQL 根據底層存儲引擎的不同,鎖的支持粒度和實現機制也不同。MyISAM 只支持表鎖,InnoDB 支持行鎖和表鎖。目前 MySQL 默認的存儲引擎是 InnoDB,這里主要介紹 InnoDB 的鎖。InnoDB 存儲引擎有兩大優點:一是支持事務;二是支持行鎖。
在高并發的情況下,MySQL 事務的并發處理會帶來幾個問題臟讀、不可重復讀、幻讀。由于高并發事務帶來這幾個問題,所以就產生了事務的隔離級別。
二、MySQL 常見的幾種鎖機制
1??實現思想
- 樂觀鎖
- 悲觀鎖
2??鎖粒度
- 表級鎖(table lock)
- 行級鎖(row lock)
3??意向鎖【表級鎖(table lock)】
意向鎖(Intention Locks)分為意向共享鎖(IS)和意向排他鎖(IX),依次表示接下來的一個事務將會獲得共享鎖還是排他鎖。意向鎖不需要顯示的獲取,在獲取共享鎖或者排他鎖的時候會自動的獲取,也就是說,如果要獲取共享鎖或者排他鎖,則一定是先獲取到了意向共享鎖或者意向排他鎖。 意向鎖不會鎖住任何東西,除非有進行全表請求的操作,否則不會鎖住任何數據。存在的意義只是用來表示有事務正在鎖某一行的數據,或者將要鎖某一行的數據。原文:Intention locks are table-level locks that indicate which type of lock (shared or exclusive) a transaction requires later for a row in a table.
IS 和 IX是表級鎖,不會和行級的 X,S 鎖發生沖突。只會和表級的 X,S 發生沖突。橫向是已經持有的鎖,縱向是正在請求的鎖:4??讀寫鎖【行級鎖(row lock)】
讀寫鎖(ReadWriteLock)即共享鎖和排他鎖。InnoDB 通過共享鎖和排他鎖兩種方式實現了標準的行鎖。共享鎖(S 鎖):允許事務獲得鎖后去讀數據。排他鎖(X 鎖):允許事務獲得鎖后去更新或刪除數據。一個事務獲取的共享鎖(S)后,允許其他事務獲取S鎖,此時兩個事務都持有共享鎖(S),但是不允許其他事務獲取X鎖。如果一個事務獲取的排他鎖(X),則不允許其他事務獲取S或者X鎖,必須等到該事務釋放鎖后才可以獲取到。
e.g.:
①LOCK TABLE mchopin READ;
用讀鎖鎖表,會阻塞其他事務修改表數據,但不會阻塞其他事務讀該表。
②LOCK TABLE mchopin WRITE;
用寫鎖鎖表,會阻塞其他事務讀和寫。
③select * from mchopin where id = 3 lock in share mode;
讀行鎖,僅對一行數據加了讀鎖。
④select * from mchopin where id = 3 for update;
寫行鎖,僅對一行數據加了寫鎖。
# T1
START TRANSACTION WITH CONSISTENT SNAPSHOT;
SELECT * FROM mchopin WHERE category_no = 2 lock in SHARE mode; //共享鎖
SELECT * FROM mchopin WHERE category_no = 2 for UPDATE; //排他鎖
COMMIT;
# T2
START TRANSACTION WITH CONSISTENT SNAPSHOT;
SELECT * FROM mchopin WHERE mchopin_no = 2 lock in SHARE mode; //共享鎖
UPDATE mchopin set mchopin_name = '動漫' WHERE mchopin_no = 2; //排他鎖
COMMIT;
5??【記錄鎖(record Locks)】
鎖住某一行,如果表存在索引,那么記錄鎖是鎖在索引上的,如果表沒有索引,那么 InnoDB 會創建一個隱藏的聚簇索引加鎖。所以在進行查詢的時候盡量采用索引進行查詢,這樣可以降低鎖的沖突。
6??【間隙鎖(Gap Locks)】
間隙鎖是一種記錄行與記錄行之間存在空隙或在第一行記錄之前或最后一行記錄之后產生的鎖。間隙鎖可能占據的單行,多行或者是空記錄。通常的情況是采用范圍查找的時候,比如在學生成績管理系統中,如果此時有學生成績 60,72,80,95,一個老師要查下成績大于 72 的所有同學的信息,采用的語句是select * from student where grade > 72 for update
,這個時候 InnoDB 鎖住的不僅是 80,95,而是所有在 72-80,80-95,以及 95 以上的所有記錄。為什么會這樣呢?因為不鎖住這些行,另一個事務在此時插入了一條分數大于 72 的記錄,會導致第一次的事務兩次查詢的結果不一樣,出現了幻讀。所以為了在滿足事務隔離級別的情況下需要鎖住所有滿足條件的行。
- 加鎖點,不是加在記錄上的,而是加在兩條記錄之間的位置。
- 作用:兩次當前讀返回的是完全相同的記錄。
幻讀和不可重復讀的關鍵點在于,幻讀是數據增加了,而不可重復讀是數據修改或刪除了。從鎖上來分析,幻讀的關鍵是 GAP 鎖,而不可重復讀的關鍵是行鎖。
7??【Next-Key Locks】
NK 是一種記錄鎖和間隙鎖的組合鎖。是 3 和 4 的組合形式,既鎖住行也鎖住間隙。并且采用的左開右閉的原則。InnoDB 對于查詢都是采用這種鎖的。
舉個例子:
CREATE TABLE `xxp` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_uid` (`uid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `xxp`(uid) VALUES (1);
INSERT INTO `xxp`(uid) VALUES (2);
INSERT INTO `xxp`(uid) VALUES (3);
INSERT INTO `xxp`(uid) VALUES (6);
INSERT INTO `xxp`(uid) VALUES (10);
# T1
START TRANSACTION WITH CONSISTENT SNAPSHOT; //1
SELECT * FROM xxp WHERE uid = 6 for UPDATE; //2
COMMIT; //5
# T2
START TRANSACTION WITH CONSISTENT SNAPSHOT; //3
INSERT INto xxp(uid) VALUES(11);
INSERT INto xxp(uid) VALUES(5); //4
INSERT INto xxp(uid) VALUES(7);
INSERT INto xxp(uid) VALUES(8);
INSERT INto xxp(uid) VALUES(9);
SELECT * FROM xxp WHERE uid = 6 for UPDATE;
COMMIT;
ROLLBACK;
按照上面 1,2,3,4 的順序執行會發現第 4 步被阻塞了,必須執行完第 5 步后才能插入成功。這里會很奇怪明明鎖住的是uid=6 的這一行,為什么不能插入 5 呢?原因就是這里采用了 next-key 的算法,鎖住的是(3,10)整個區間。