靈魂發(fā)問(wèn):MySQL是如何解決幻讀的?
閱讀本文大概需要 5 分鐘。
來(lái)自:blog.csdn.net/sinat_27143551/article/details/81736330
概念
MySQL InnoDB支持三種行鎖定方式:
行鎖(Record Lock):鎖直接加在索引記錄上面。 間隙鎖(Gap Lock):鎖加在不存在的空閑空間,可以是兩個(gè)索引記錄之間,也可能是第一個(gè)索引記錄之前或最后一個(gè)索引之后的空間。 Next-Key Lock:行鎖與間隙鎖組合起來(lái)用就叫做 Next-Key Lock。
當(dāng)我們用范圍條件而不是相等條件檢索數(shù)據(jù),并請(qǐng)求共享或排他鎖時(shí),InnoDB會(huì)給符合條件的已有數(shù)據(jù)記錄的索引項(xiàng)加鎖;對(duì)于鍵值在條件范圍內(nèi)但并不存在的記錄,叫做“間隙(GAP)”,InnoDB也會(huì)對(duì)這個(gè)“間隙”加鎖,這種鎖機(jī)制就是所謂的間隙鎖(Next-Key鎖)。
舉例來(lái)說(shuō),假如user表中只有101條記錄,其empid的值分別是 1,2,...,100,101,下面的SQL:
select * from user where user_id > 100 for update;
是一個(gè)范圍條件的檢索,InnoDB不僅會(huì)對(duì)符合條件的user_id值為101的記錄加鎖,也會(huì)對(duì)user_id大于101(這些記錄并不存在)的“間隙”加鎖。
產(chǎn)生幻讀的原因是,行鎖只能鎖住行,但是新插入記錄這個(gè)動(dòng)作,要更新的是記錄之間的“間隙”。因此,為了解決幻讀問(wèn)題,InnoDB 只好引入新的鎖,也就是間隙鎖 (Gap Lock)。
InnoDB使用間隙鎖的目的,一方面是為了防止幻讀,以滿(mǎn)足相關(guān)隔離級(jí)別的要求,對(duì)于上面的例子,要是不使用間隙鎖,如果其他事務(wù)插入了user_id大于100的任何記錄,那么本事務(wù)如果再次執(zhí)行上述語(yǔ)句,就會(huì)發(fā)生幻讀;另外一方面,是為了滿(mǎn)足其恢復(fù)和復(fù)制的需要
快照讀和當(dāng)前讀
快照讀歷史數(shù)據(jù)-mvcc
innodb的默認(rèn)事務(wù)隔離級(jí)別是rr(可重復(fù)讀)。它的實(shí)現(xiàn)技術(shù)是mvcc(MVCC只在讀提交可重復(fù)讀兩種隔離級(jí)別下工作)。基于版本的控制協(xié)議。該技術(shù)不僅可以保證innodb的可重復(fù)讀,而且可以防止幻讀。但是它防止的是快照讀,也就是讀取的數(shù)據(jù)雖然是一致的,但是數(shù)據(jù)是歷史數(shù)據(jù)。
當(dāng)前讀最新數(shù)據(jù)-next-key lock
如何做到保證數(shù)據(jù)是一致的(也就是一個(gè)事務(wù),其內(nèi)部讀取對(duì)應(yīng)某一個(gè)數(shù)據(jù)的時(shí)候,數(shù)據(jù)都是一樣的),同時(shí)讀取的數(shù)據(jù)是最新的數(shù)據(jù)。innodb提供了next-key lock,也就是結(jié)合gap鎖與行鎖,達(dá)到最終目的。
實(shí)現(xiàn):
1.快照讀(snapshot read)
簡(jiǎn)單的select操作(不包括 select ... lock in share mode, select ... for update)
2.當(dāng)前讀(current read)
select ... lock in share mode、select ... for update
insert、update、delete
在RR級(jí)別下,快照讀是通過(guò)MVCC(多版本控制)和undo log來(lái)實(shí)現(xiàn)的,當(dāng)前讀是通過(guò)加record lock(記錄鎖)和gap lock(間隙鎖)來(lái)實(shí)現(xiàn)的。
測(cè)試
建表innodb_lock:
DROP TABLE IF EXISTS `innodb_lock`;
CREATE TABLE `innodb_lock` (
`a` int(10) NOT NULL,
`b` varchar(255) NOT NULL DEFAULT '',
KEY `index_a` (`a`),
KEY `index_b` (`b`)
) ENGINE=InnoDB;
插入數(shù)據(jù),注意這里邊沒(méi)有a為2的數(shù)據(jù):
INSERT INTO `innodb_lock` VALUES ('1', 'b2');
INSERT INTO `innodb_lock` VALUES ('3', '3');
INSERT INTO `innodb_lock` VALUES ('4', '4000');
INSERT INTO `innodb_lock` VALUES ('5', '5000');
INSERT INTO `innodb_lock` VALUES ('6', '6000');
INSERT INTO `innodb_lock` VALUES ('7', '7000');
INSERT INTO `innodb_lock` VALUES ('8', '8000');
INSERT INTO `innodb_lock` VALUES ('9', '9000');
(1)開(kāi)啟兩個(gè)客戶(hù)端,修改事務(wù)隔離級(jí)別為可重復(fù)讀

(2)開(kāi)啟事務(wù),在左側(cè)客戶(hù)端批量修改a為1~6范圍內(nèi)的數(shù)據(jù)。在右側(cè)客戶(hù)端插入a為2的數(shù)據(jù)。右側(cè)操作被阻塞。說(shuō)明有間隙鎖。

(3)重復(fù)(2),事務(wù)隔離級(jí)別依然是repeatable read,只不過(guò)變成在右側(cè)客戶(hù)端插入a為10的數(shù)據(jù),成功。

(4)事務(wù)隔離級(jí)別設(shè)置為read committed,重復(fù)步驟(2),發(fā)現(xiàn)右側(cè)客戶(hù)端的操作成功,說(shuō)明該隔離級(jí)別無(wú)間隙鎖。

(5)還要特別說(shuō)明的是,InnoDB除了通過(guò)范圍條件加鎖時(shí)使用間隙鎖外,如果使用相等條件請(qǐng)求給一個(gè)不存在的記錄加鎖,InnoDB也會(huì)使用間隙鎖!左側(cè)客戶(hù)端給不存在的記錄加鎖,右側(cè)客戶(hù)端的增加操作阻塞。

但是,如果a是唯一索引,不會(huì)升級(jí)全表鎖。
先添加唯一索引:


(6)重復(fù)步驟(5),發(fā)現(xiàn)右側(cè)客戶(hù)端不會(huì)被阻塞,數(shù)據(jù)插入成功

小結(jié)
Next-Key Lock的方式,也就是說(shuō)不會(huì)對(duì)索引之間的間隙加鎖,仔細(xì)想想的話,這個(gè)并不難理解,大家也可以自己測(cè)試一下。innodb_locks_unsafe_for_binlog。補(bǔ)充
MySQL InnoDB的可重復(fù)讀并不保證避免幻讀,需要應(yīng)用使用加鎖讀來(lái)保證。而這個(gè)加鎖度使用到的機(jī)制就是next-key locks。t Session A Session B
|
| START TRANSACTION; START TRANSACTION;
|
| SELECT * FROM innodb_lock;
| +------+-------+
| | a | b |
| +------+-------+
| | 1 | a |
| +------+-------+
| INSERT INTO innodb_lock
| VALUES (2, 'b');
|
| SELECT * FROM innodb_lock;
| +------+-------+
| | a | b |
| +------+-------+
| | 1 | a |
| +------+-------+
| COMMIT;
|
| SELECT * FROM innodb_lock;
| +------+-------+
| | a | b |
| +------+-------+
| | 1 | a |
| +------+-------+
|
| UPDATE innodb_lock SET b='z';
| Rows matched: 2 Changed: 2 Warnings: 0
| (怎么多出來(lái)一行)
|
| SELECT * FROM innodb_lock;
| +------+-------+
| | a | b |
| +------+-------+
| | 1 | z |
| | 2 | z |
| +------+-------+
|
推薦閱讀:
扔掉工具類(lèi)!MyBatis 一個(gè)簡(jiǎn)單配置搞定加密、解密,不能太方便了~!
實(shí)現(xiàn)一個(gè)小輪子—用AOP實(shí)現(xiàn)異步上傳
互聯(lián)網(wǎng)初中高級(jí)大廠面試題(9個(gè)G) 內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬(wàn)并發(fā)、消息隊(duì)列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper......等技術(shù)棧!
?戳閱讀原文領(lǐng)?。?/span> 朕已閱


