自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

記錄鎖、間隙鎖與 Next-Key Lock

數(shù)據(jù)庫(kù) MySQL
現(xiàn)在 Gap Lock 間隙鎖,就是要把這些記錄之間的間隙也給鎖住,間隙鎖住了,就不用擔(dān)心幻讀問(wèn)題了,這也是 Gap Lock 存在的意義。

有小伙伴在微信上表示面試時(shí)被問(wèn)到了 Next-Key Lock 是啥,結(jié)果一臉懵逼,那么今天我們來(lái)捋一捋 MySQL 中的記錄鎖、間隙鎖以及 Next-Key Lock。

1. Record LockRecord

Lock 也就是我們所說(shuō)的記錄鎖,記錄鎖是對(duì)索引記錄的鎖,注意,它是針對(duì)索引記錄,即它只鎖定記錄這一行數(shù)據(jù)。

例如如下一條 SQL:

select * from user where id=1 for update;

注意,id 是索引,id 如果不是索引,上面這條 SQL 所加的排他鎖就不是一個(gè) Record Lock。

我們來(lái)看如下一個(gè)例子:

首先我們將系統(tǒng)變量 innodb_status_output_locks 設(shè)置為 ON,如下:

接下來(lái)我們執(zhí)行如下 SQL,鎖定一行數(shù)據(jù),此時(shí)會(huì)自動(dòng)為表加上 IX 鎖:

接下來(lái)我們?cè)谝粋€(gè)新的會(huì)話中執(zhí)行如下指令來(lái)查看 InnoDB 存儲(chǔ)引擎的情況:

show engine innodb status\G

輸出的信息很多,我們重點(diǎn)關(guān)注 TRANSACTIONS,如下:

可以看到:

  • TABLE LOCK table test08.user trx id 3564804 lock mode IX:這句就是說(shuō)事務(wù) id 為 3564804 的事務(wù),為 user 表添加了意向排他鎖(IX)。
  • RECORD LOCKS space id 851 page no 3 n bits 80 index PRIMARY of table test08.user trx id 3564804 lock_mode X locks rec but not gap:這個(gè)就是一個(gè)鎖結(jié)構(gòu)的記錄,這里的索引是 PRIMARY,加的鎖也是正兒八經(jīng)的記錄鎖(not gap)。

看到了 LOCKS REC BUT NOT GAP,就說(shuō)明這是一個(gè)記錄鎖。

那么這個(gè) Record Lock 和我們之前所講的 S 鎖以及 X 鎖有什么區(qū)別呢?S 鎖是共享鎖,X 鎖是排他鎖,當(dāng)我們加 S 鎖或者 X 鎖的時(shí)候,如果用到了索引,鎖加在了某一條具體的記錄上,那么這個(gè)鎖也是一個(gè)記錄鎖(其實(shí),記錄鎖,S 鎖,X 鎖,概念有一些重復(fù)的地方,但是描述的重點(diǎn)不一樣)。

或者也可以理解為記錄鎖又細(xì)分為 S 鎖和 X 鎖,它們之間的兼容性如下圖:

兼容性

S 型記錄鎖

X 型記錄鎖

S 型記錄鎖

兼容

不兼容

X 型記錄鎖

不兼容

不兼容

2. Gap Lock

Gap Lock 也叫做間隙鎖,它的存在可以解決幻讀問(wèn)題,另外需要注意,Gap Lock 也只在 REPEATABLE READ 隔離級(jí)別下有效。先來(lái)看看什么是幻讀,我們來(lái)看如下一個(gè)表格:

有兩個(gè)會(huì)話,A 和 B,先在會(huì)話 A 中開(kāi)啟事務(wù),然后查詢 age 為 99 的用戶總數(shù),注意使用當(dāng)前讀,因?yàn)樵谀J(rèn)的隔離級(jí)別下,默認(rèn)的快照讀并不能讀到其他事務(wù)提交的數(shù)據(jù),至于快照讀和當(dāng)前讀的區(qū)別,大家參考:S 鎖與 X 鎖,當(dāng)前讀與快照讀!。當(dāng)會(huì)話 A 中第一次查詢過(guò)后,會(huì)話 B 中向數(shù)據(jù)庫(kù)添加了一行記錄,等到會(huì)話 A 中第二次查詢的時(shí)候,就查到了和第一次查詢不一樣的結(jié)果,這就是幻讀(注意幻讀專指數(shù)據(jù)插入引起的不一致)。

在 MySQL 默認(rèn)的隔離級(jí)別 REPEATABLE READ 下,上圖所描述的情況無(wú)法復(fù)現(xiàn)。無(wú)法復(fù)現(xiàn)的原因在于,在 MySQL 的 REPEATABLE READ 隔離級(jí)別中,它已經(jīng)幫我們解決了幻讀問(wèn)題,解決的方案就是 Gap Lock。

大家想想,之所以出現(xiàn)幻讀的問(wèn)題,是因?yàn)橛涗浿g存在縫隙,用戶可以往這些縫隙中插入數(shù)據(jù),這就導(dǎo)致了幻讀問(wèn)題,如下圖:

如圖所示,id 之間有縫隙,有縫隙就有漏洞。前面我們所說(shuō)的記錄鎖只能鎖住一條具體的記錄,但是對(duì)于記錄之間的空隙卻無(wú)能無(wú)力,這就導(dǎo)致了幻讀(其他事務(wù)可往縫隙中插入數(shù)據(jù))。

現(xiàn)在 Gap Lock 間隙鎖,就是要把這些記錄之間的間隙也給鎖住,間隙鎖住了,就不用擔(dān)心幻讀問(wèn)題了,這也是 Gap Lock 存在的意義。

給一條記錄加 Gap Lock,是鎖住了這條記錄前面的空隙,例如給 id 為 1 的記錄加 Gap Lock,鎖住的范圍是 (-∞,1),給 id 為 3 的記錄加 Gap Lock,鎖住的范圍是 (1,3),那么 id 為 10 后面的空隙怎么鎖定呢?MySQL 提供了一個(gè) Supremum 表示當(dāng)前頁(yè)面中的最大記錄,所以最后針對(duì) Supremum 鎖住的范圍就是 (10,+∞),這樣,所有的間隙都被覆蓋到了,由于鎖定的是間隙,所以都是開(kāi)區(qū)間。

那么我們?cè)趺礃幽芸吹?Gap Lock 呢?我給大家舉一個(gè)簡(jiǎn)單的例子,假設(shè)我有如下一張表:

CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `age` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

一個(gè)簡(jiǎn)單的表,id 是主鍵,age 是普通索引,表中有如下幾條記錄:

接下來(lái)我們執(zhí)行如下 SQL,鎖定一行數(shù)據(jù),此時(shí)也會(huì)產(chǎn)生間隙鎖:

接下來(lái)我們?cè)谝粋€(gè)新的會(huì)話中執(zhí)行如下指令來(lái)查看 InnoDB 存儲(chǔ)引擎的情況:

show engine innodb status\G

輸出的信息很多,我們重點(diǎn)關(guān)注 TRANSACTIONS,如下:

紅色框選中的,就是一個(gè)間隙鎖的加鎖記錄,可以看到,在某一個(gè)記錄之前加了間隙鎖。

這就是間隙鎖。非常重要的一點(diǎn)需要大家牢記:Gap Lock 只在 REPEATABLE READ 隔離級(jí)別下有效。

3. Next-Key Lock

以下內(nèi)容都是基于 MySQL 默認(rèn)的隔離級(jí)別 REPEATABLE READ。

如果我們既想鎖定一行,又想鎖定行之間的記錄,那么就是 Next-Key Lock 了,換言之,Next-Key Lock 是 Record Lock 和 Gap Lock 的結(jié)合體。

正常來(lái)說(shuō),我們加行鎖的基本單位就是 Next-Key Lock,即既有記錄鎖又有間隙鎖,但是有時(shí)候 Next-Key Lock 會(huì)退化,我們通過(guò)幾個(gè)簡(jiǎn)單的例子來(lái)分析一下。

首先我們來(lái)看看 Next-Key Lock 的加鎖規(guī)則:

  • 鎖的范圍是左開(kāi)右閉。
  • 如果是唯一非空索引的等值查詢,Next-Key Lock 會(huì)退化成 Record Lock。
  • 普通索引上的等值查詢,向后遍歷時(shí),最后一個(gè)不滿足等值條件的時(shí)候,Next-Key Lock 會(huì)退化成 Gap Lock。

我們通過(guò)幾個(gè)簡(jiǎn)單的例子來(lái)分析下。

3.1 唯一非空索引

假設(shè)我有一個(gè)學(xué)生表,學(xué)生表中有學(xué)生的姓名和成績(jī),如下:

CREATE TABLE `student` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`score` double NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `score` (`score`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

id 是主鍵,score 是成績(jī),其中 score 是唯一非空索引。

現(xiàn)在表中有如下數(shù)據(jù):

假設(shè)我們執(zhí)行如下 SQL:

在這個(gè)例子中,由于 score 是唯一非空索引,所以 Next-Key Lock 會(huì)退化成 Record Lock,換句話說(shuō),這行 SQL 只給 score 為 90 的記錄加鎖,不存在 Gap Lock,即我們新開(kāi)一個(gè)會(huì)話,插入一條 score 為 88 的記錄也是 OK 的。

不過(guò)這里有一個(gè)特例,如果鎖定的是一個(gè)不存在的記錄,那么也會(huì)產(chǎn)生間隙鎖,例如下面這個(gè):

由于并不存在 score 為 91 的記錄,所以這里會(huì)產(chǎn)生一個(gè)范圍為 (90,95) 的間隙鎖,我們執(zhí)行如下 SQL 可以驗(yàn)證:

可以看到,90.1、94.9 都會(huì)被阻塞(我按了 Ctrl C,所以大家看到查詢終止)。

90、95 則不符合唯一非空索引的條件。

95.1 則可以插入成功。

沒(méi)問(wèn)題。

3.2 非空索引

現(xiàn)在我們重新開(kāi)始,將 score 索引改為普通索引,如下:

CREATE TABLE `student` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`score` double NOT NULL,
PRIMARY KEY (`id`),
KEY `score` (`score`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

數(shù)據(jù)還是跟前面一樣,此時(shí)我們來(lái)執(zhí)行如下 SQL:

我們來(lái)分析下。

此時(shí)要鎖定的是 id 為 90 的記錄,那么首先加間隙鎖,上一個(gè) score 為 89,所以這次加的間隙鎖范圍是 (89,90),同時(shí)要鎖定 id 為 90 的記錄,所以進(jìn)一步優(yōu)化為 (89,90]。

同時(shí),這里還有一條規(guī)則,就是滿足條件的上一條記錄,也需要被鎖住,所以最終的鎖范圍就是 [89,90]。

由于 score 不是唯一性索引,所以還需要繼續(xù)向后查找,找到的下一條記錄是 95,由于此時(shí) Next-Key Lock 會(huì)退化成 Gap Lock,所以鎖定的范圍是 (90,95)。綜上,最終鎖定的范圍是 [89,95)。

接下來(lái)我們可以新開(kāi)一個(gè)會(huì)話,我們分別嘗試添加如下數(shù)據(jù)看看是否能夠添加成功:

可以看到,score 為 88 是可以的,但是為 89.1 就不行。

score 為 95 也是可以的,但是為 94.9 就不行。

再試一下 89 是否可以:

說(shuō)明我們上面分析的加鎖范圍是正確的。

再來(lái)看如下一條 SQL:

跟前面的案例相比,這次多了 limit 1,limit 1 表示只要一條記錄,所以這次查找到 90 之后就不會(huì)再往后查找了,那么最終的鎖就是間隙鎖+一個(gè)記錄鎖,最終的范圍就是 [89,90]。

此時(shí)新開(kāi)一個(gè)會(huì)話,分別插入 score 為 88.9、89、90、91 的 記錄,驗(yàn)證我們上面所分析的加鎖范圍:

88.9 和 89 的插入結(jié)果跟我們預(yù)想的一致。

可以看到,這里 90 也能插入,能插入的原因是因?yàn)槿狈?90 往后的間隙鎖。

4. 小結(jié)

MySQL 中的鎖有點(diǎn)繁雜,小伙伴們可以趁著某個(gè)周末,花點(diǎn)時(shí)間捋一捋,以后面試再遇到這些問(wèn)題的時(shí)候就不頭大了。

責(zé)任編輯:武曉燕 來(lái)源: 江南一點(diǎn)雨
相關(guān)推薦

2025-04-24 10:56:01

MySQLInnoDB數(shù)據(jù)庫(kù)鎖

2024-06-12 14:03:31

MySQLInnoDB

2021-06-08 09:41:26

MySQL加鎖范圍

2021-06-05 18:02:20

MySQL加鎖范圍

2023-06-05 08:15:30

MySQLInnoDB

2022-10-24 08:02:14

MySQL索引類型

2023-11-06 08:35:08

表鎖行鎖間隙鎖

2023-12-06 07:33:20

MySQL鎖事間隙鎖

2020-10-20 13:50:47

MySQL數(shù)據(jù)庫(kù)

2021-12-14 08:10:00

MySQL行鎖間隙鎖

2020-07-02 08:22:56

MySQL間隙鎖過(guò)行鎖

2024-01-16 12:19:08

MySQL重要機(jī)制高并發(fā)

2011-11-28 12:55:37

JavaJVM

2024-05-17 09:33:22

樂(lè)觀鎖CASversion

2013-12-19 13:25:40

InnoDB數(shù)據(jù)庫(kù)

2023-03-26 21:51:42

2010-09-08 14:49:12

SQL Server數(shù)據(jù)庫(kù)

2024-03-11 00:00:00

mysqlInnoDB幻讀

2022-03-18 10:44:46

S 鎖X 鎖快照

2023-02-23 10:32:52

樂(lè)觀鎖
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)