精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

MySQL 加行級鎖的規(guī)則終于被我說清楚了!

數(shù)據(jù)庫 MySQL
這次我以 MySQL 8.0.26 版本,在可重復(fù)讀隔離級別之下,做了幾個(gè)實(shí)驗(yàn),讓大家了解了唯一索引和非唯一索引的行級鎖的加鎖規(guī)則。

大家好,我是小林。

是不是很多人都對 MySQL 加行級鎖的規(guī)則搞的迷迷糊糊,一會是 next-key 鎖,一會是間隙鎖,一會又是記錄鎖。

坦白說,確實(shí)還挺復(fù)雜的,但是好在我找點(diǎn)了點(diǎn)規(guī)律,也知道如何如何用命令分析加了什么類型的行級鎖。

之前我寫過一篇關(guān)于「MySQL 是怎么加行級鎖的?」的文章,隨著我寫 MySQL 鎖相關(guān)的文章越來越多時(shí),后來發(fā)現(xiàn)當(dāng)時(shí)的文章寫的不夠詳細(xì)。

為了讓大家很清楚的知道 MySQL 是怎么加行級鎖的,以及如何用命令分析加了什么行級鎖,所以我重寫了這篇文章。

文章內(nèi)容比較長,大家可以耐心看下去,一定會有新的發(fā)現(xiàn)!

圖片

目錄

什么 SQL 語句會加行級鎖?

InnoDB 引擎是支持行級鎖的,而 MyISAM 引擎并不支持行級鎖,所以后面的內(nèi)容都是基于 InnoDB 引擎 的。

普通的 select 語句是不會對記錄加鎖的,因?yàn)樗鼘儆诳煺兆x,是通過  MVCC(多版本并發(fā)控制)實(shí)現(xiàn)的。

如果要在查詢時(shí)對記錄加行級鎖,可以使用下面這兩個(gè)方式,這兩種查詢會加鎖的語句稱為鎖定讀。

//對讀取的記錄加共享鎖(S型鎖)
select ... lock in share mode;

//對讀取的記錄加獨(dú)占鎖(X型鎖)
select ... for update;

上面這兩條語句必須在一個(gè)事務(wù)中,因?yàn)楫?dāng)事務(wù)提交了,鎖就會被釋放,所以在使用這兩條語句的時(shí)候,要加上 begin 或者 start transaction 開啟事務(wù)的語句。

除了上面這兩條鎖定讀語句會加行級鎖之外,update 和 delete 操作都會加行級鎖,且鎖的類型都是獨(dú)占鎖。

//對操作的記錄加獨(dú)占鎖(X型鎖)
updaet table .... where id = 1;

//對操作的記錄加獨(dú)占鎖(X型鎖)
delete from table where id = 1;

共享鎖(S鎖)滿足讀讀共享,讀寫互斥。獨(dú)占鎖(X鎖)滿足寫寫互斥、讀寫互斥。

圖片

行級鎖有哪些種類?

不同隔離級別下,行級鎖的種類是不同的。

在讀已提交隔離級別下,行級鎖的種類只有記錄鎖,也就是僅僅把一條記錄鎖上。

在可重復(fù)讀隔離級別下,行級鎖的種類除了有記錄鎖,還有間隙鎖(目的是為了避免幻讀),所以行級鎖的種類主要有三類:

  • Record Lock,記錄鎖,也就是僅僅把一條記錄鎖上;
  • Gap Lock,間隙鎖,鎖定一個(gè)范圍,但是不包含記錄本身;
  • Next-Key Lock:Record Lock + Gap Lock 的組合,鎖定一個(gè)范圍,并且鎖定記錄本身。

接下來,分別介紹這三種行級鎖。

Record Lock

Record Lock 稱為記錄鎖,鎖住的是一條記錄。而且記錄鎖是有 S 鎖和 X 鎖之分的:

  • 當(dāng)一個(gè)事務(wù)對一條記錄加了 S 型記錄鎖后,其他事務(wù)也可以繼續(xù)對該記錄加 S 型記錄鎖(S 型與 S 鎖兼容),但是不可以對該記錄加 X 型記錄鎖(S 型與 X 鎖不兼容);
  • 當(dāng)一個(gè)事務(wù)對一條記錄加了 X 型記錄鎖后,其他事務(wù)既不可以對該記錄加 S 型記錄鎖(S 型與 X 鎖不兼容),也不可以對該記錄加 X 型記錄鎖(X 型與 X 鎖不兼容)。

舉個(gè)例子,當(dāng)一個(gè)事務(wù)執(zhí)行了下面這條語句:

mysql > begin;
mysql > select * from t_test where id = 1 for update;

事務(wù)會對表中主鍵 id = 1 的這條記錄加上 X 型的記錄鎖,這樣其他事務(wù)就無法對這條記錄進(jìn)行修改和刪除了。

圖片

當(dāng)事務(wù)執(zhí)行 commit 后,事務(wù)過程中生成的鎖都會被釋放。

Gap Lock

Gap Lock 稱為間隙鎖,只存在于可重復(fù)讀隔離級別,目的是為了解決可重復(fù)讀隔離級別下幻讀的現(xiàn)象。

假設(shè),表中有一個(gè)范圍 id 為(3,5)間隙鎖,那么其他事務(wù)就無法插入 id = 4 這條記錄了,這樣就有效的防止幻讀現(xiàn)象的發(fā)生。

圖片

間隙鎖雖然存在 X 型間隙鎖和 S 型間隙鎖,但是并沒有什么區(qū)別,間隙鎖之間是兼容的,即兩個(gè)事務(wù)可以同時(shí)持有包含共同間隙范圍的間隙鎖,并不存在互斥關(guān)系,因?yàn)殚g隙鎖的目的是防止插入幻影記錄而提出的。

Next-Key Lock

Next-Key Lock 稱為臨鍵鎖,是 Record Lock + Gap Lock 的組合,鎖定一個(gè)范圍,并且鎖定記錄本身。

假設(shè),表中有一個(gè)范圍 id 為(3,5] 的 next-key lock,那么其他事務(wù)即不能插入 id = 4 記錄,也不能修改 id = 5 這條記錄。

圖片

所以,next-key lock 即能保護(hù)該記錄,又能阻止其他事務(wù)將新記錄插入到被保護(hù)記錄前面的間隙中。

next-key lock 是包含間隙鎖+記錄鎖的,如果一個(gè)事務(wù)獲取了 X 型的 next-key lock,那么另外一個(gè)事務(wù)在獲取相同范圍的 X 型的 next-key lock 時(shí),是會被阻塞的。

比如,一個(gè)事務(wù)持有了范圍為 (1, 10] 的 X 型的 next-key lock,那么另外一個(gè)事務(wù)在獲取相同范圍的 X 型的 next-key lock 時(shí),就會被阻塞。

雖然相同范圍的間隙鎖是多個(gè)事務(wù)相互兼容的,但對于記錄鎖,我們是要考慮 X 型與 S 型關(guān)系,X 型的記錄鎖與 X 型的記錄鎖是沖突的。

MySQL 是怎么加行級鎖的?

行級鎖加鎖規(guī)則比較復(fù)雜,不同的場景,加鎖的形式是不同的。

加鎖的對象是索引,加鎖的基本單位是 next-key lock,它是由記錄鎖和間隙鎖組合而成的,next-key lock 是前開后閉區(qū)間,而間隙鎖是前開后開區(qū)間。

但是,next-key lock 在一些場景下會退化成記錄鎖或間隙鎖。

那到底是什么場景呢?

這次會以下面這個(gè)表結(jié)構(gòu)來進(jìn)行實(shí)驗(yàn)說明:

CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL,
`age` int NOT NULL,
PRIMARY KEY (`id`),
KEY `index_age` (`age`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

其中,id 是主鍵索引(唯一索引),age 是普通索引(非唯一索引),name 是普通的列。

表中的有這些行記錄:

圖片

這次實(shí)驗(yàn)環(huán)境的 MySQL 版本是 8.0.26,隔離級別是「可重復(fù)讀」。

不同版本的加鎖規(guī)則可能是不同的,但是大體上是相同的。

唯一索引等值查詢

當(dāng)我們用唯一索引進(jìn)行等值查詢的時(shí)候,查詢的記錄存不存在,加鎖的規(guī)則也會不同:

  • 當(dāng)查詢的記錄是「存在」的,在索引樹上定位到這一條記錄后,將該記錄的索引中的 next-key lock 會退化成「記錄鎖」。
  • 當(dāng)查詢的記錄是「不存在」的,則會在索引樹找到第一條大于該查詢記錄的記錄,然后將該記錄的索引中的 next-key lock 會退化成「間隙鎖」。

接下里用兩個(gè)案例來說明。

1、記錄存在的情況

假設(shè)事務(wù) A 執(zhí)行了這條等值查詢語句,查詢的記錄是「存在」于表中的。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id = 1 for update;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 1 | 路飛 | 19 |
+----+--------+-----+
1 row in set (0.02 sec)

那么,事務(wù) A 會為 id 為 1 的這條記錄就會加上 X 型的記錄鎖。

圖片

接下來,如果有其他事務(wù),對 id 為 1 的記錄進(jìn)行更新或者刪除操作的話,這些操作都會被阻塞,因?yàn)楦禄蛘邉h除操作也會對記錄加 X 型的記錄鎖,而 X 鎖和 X 鎖之間是互斥關(guān)系。

比如,下面這個(gè)例子:

圖片

因?yàn)槭聞?wù) A 對 id = 1的記錄加了 X 型的記錄鎖,所以事務(wù) B 在修改 id=1 的記錄時(shí)會被阻塞,事務(wù) C 在刪除 id=1 的記錄時(shí)也會被阻塞。

有什么命令可以分析加了什么鎖?

我們可以通過 select * from performance_schema.data_locks\G; 這條語句,查看事務(wù)執(zhí)行 SQL 過程中加了什么鎖。

我們以前面的事務(wù) A 作為例子,分析下下它加了什么鎖。

圖片

從上圖可以看到,共加了兩個(gè)鎖,分別是:

  • 表鎖:X 類型的意向鎖;
  • 行鎖:X 類型的記錄鎖;

這里我們重點(diǎn)關(guān)注行級鎖,圖中 LOCK_TYPE 中的 RECORD 表示行級鎖,而不是記錄鎖的意思。

通過 LOCK_MODE 可以確認(rèn)是 next-key 鎖,還是間隙鎖,還是記錄鎖:

  • 如果 LOCK_MODE 為X,說明是 next-key 鎖;
  • 如果 LOCK_MODE 為X, REC_NOT_GAP,說明是記錄鎖;
  • 如果 LOCK_MODE 為X, GAP,說明是間隙鎖;

因此,此時(shí)事務(wù) A 在 id = 1 記錄的主鍵索引上加的是記錄鎖,鎖住的范圍是 id 為 1 的這條記錄。這樣其他事務(wù)就無法對 id 為 1 的這條記錄進(jìn)行更新和刪除操作了。

從這里我們也可以得知,加鎖的對象是針對索引,因?yàn)檫@里查詢語句掃描的 B+ 樹是聚簇索引樹,即主鍵索引樹,所以是對主鍵索引加鎖。將對應(yīng)記錄的主鍵索引加 記錄鎖后,就意味著其他事務(wù)無法對該記錄進(jìn)行更新和刪除操作了。

2、記錄不存在的情況

假設(shè)事務(wù) A 執(zhí)行了這條等值查詢語句,查詢的記錄是「不存在」于表中的。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id = 2 for update;
Empty set (0.03 sec)

接下來,通過 select * from performance_schema.data_locks\G; 這條語句,查看事務(wù)執(zhí)行 SQL 過程中加了什么鎖。

圖片

從上圖可以看到,共加了兩個(gè)鎖,分別是:

  • 表鎖:X 類型的意向鎖;
  • 行鎖:X 類型的間隙鎖;

因此,此時(shí)事務(wù) A 在 id = 5 記錄的主鍵索引上加的是間隙鎖,鎖住的范圍是 (1, 5)。

圖片

接下來,如果有其他事務(wù)插入 id 值為 2、3、4 這一些記錄的話,這些插入語句都會發(fā)生阻塞。

注意,如果其他事務(wù)插入的 id = 1 或者 id = 5 的記錄話,并不會發(fā)生阻塞,而是報(bào)主鍵沖突的錯(cuò)誤,因?yàn)楸碇幸呀?jīng)存在 id = 1 和 id = 5 的記錄了。

比如,下面這個(gè)例子:

圖片

因?yàn)槭聞?wù) A 在 id = 5 記錄的主鍵索引上加了范圍為 (1, 5) 的 X 型間隙鎖,所以事務(wù) B 在插入一條 id 為 3 的記錄時(shí)會被阻塞住,即無法插入 id = 3 的記錄。

間隙鎖的范圍(1, 5) ,是怎么確定的?

根據(jù)我的經(jīng)驗(yàn),如果 LOCK_MODE 是 next-key 鎖或者間隙鎖,那么 LOCK_DATA 就表示鎖的范圍「右邊界」,此次的事務(wù) A 的 LOCK_DATA 是 5。

然后鎖范圍的「左邊界」是表中 id 為 5 的上一條記錄的 id 值,即 1。

因此,間隙鎖的范圍(1, 5)。

唯一索引范圍查詢

范圍查詢和等值查詢的加鎖規(guī)則是不同的。

當(dāng)唯一索引進(jìn)行范圍查詢時(shí),會對每一個(gè)掃描到的索引加 next-key 鎖,然后如果遇到下面這些情況,會退化成記錄鎖或者間隙鎖:

  • 情況一:針對「大于等于」的范圍查詢,因?yàn)榇嬖诘戎挡樵兊臈l件,那么如果等值查詢的記錄是存在于表中,那么該記錄的索引中的 next-key 鎖會退化成記錄鎖。
  • 情況二:針對「小于或者小于等于」的范圍查詢,要看條件值的記錄是否存在于表中:

當(dāng)條件值的記錄不在表中,那么不管是「小于」還是「小于等于」條件的范圍查詢,掃描到終止范圍查詢的記錄時(shí),該記錄的索引的 next-key 鎖會退化成間隙鎖,其他掃描到的記錄,都是在這些記錄的索引上加 next-key 鎖。

當(dāng)條件值的記錄在表中,如果是「小于」條件的范圍查詢,掃描到終止范圍查詢的記錄時(shí),該記錄的索引的 next-key 鎖會退化成間隙鎖,其他掃描到的記錄,都是在這些記錄的索引上加 next-key 鎖;如果「小于等于」條件的范圍查詢,掃描到終止范圍查詢的記錄時(shí),該記錄的索引 next-key 鎖不會退化成間隙鎖。其他掃描到的記錄,都是在這些記錄的索引上加 next-key 鎖。

接下來,通過幾個(gè)實(shí)驗(yàn),才驗(yàn)證我上面說的結(jié)論。

1、針對「大于或者大于等于」的范圍查詢

實(shí)驗(yàn)一:針對「大于」的范圍查詢的情況。

假設(shè)事務(wù) A 執(zhí)行了這條范圍查詢語句:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id > 15 for update;
+----+-----------+-----+
| id | name | age |
+----+-----------+-----+
| 20 | 香克斯 | 39 |
+----+-----------+-----+
1 row in set (0.01 sec)

事務(wù) A 加鎖變化過程如下:

  • 最開始要找的第一行是 id = 20,由于查詢該記錄不是一個(gè)等值查詢(不是大于等于條件查詢),所以對該主鍵索引加的是范圍為  (15, 20] 的 next-key 鎖;
  • 由于是范圍查找,就會繼續(xù)往后找存在的記錄,雖然我們看見表中最后一條記錄是 id = 20 的記錄,但是實(shí)際在 Innodb 存儲引擎中,會用一個(gè)特殊的記錄來標(biāo)識最后一條記錄,該特殊的記錄的名字叫 supremum pseudo-record ,所以掃描第二行的時(shí)候,也就掃描到了這個(gè)特殊記錄的時(shí)候,會對該主鍵索引加的是范圍為  (20, +∞] 的 next-key 鎖。
  • 停止掃描。

可以得知,事務(wù) A 在主鍵索引上加了兩個(gè) X 型 的 next-key 鎖:

圖片

  • 在 id = 20 這條記錄的主鍵索引上,加了范圍為 (15, 20] 的 next-key 鎖,意味著其他事務(wù)即無法更新或者刪除 id = 20 的記錄,同時(shí)無法插入 id 值為 16、17、18、19 的這一些新記錄。
  • 在特殊記錄( supremum pseudo-record)的主鍵索引上,加了范圍為 (20, +∞] 的 next-key 鎖,意味著其他事務(wù)無法插入 id 值大于 20 的這一些新記錄。

我們也可以通過 select * from performance_schema.data_locks\G; 這條語句來看看事務(wù) A 加了什么鎖。

輸出結(jié)果如下,我這里只截取了行級鎖的內(nèi)容。

圖片

從上圖中的分析中,也可以得到事務(wù) A 在主鍵索引上加了兩個(gè) X 型 的next-key 鎖:

  • 在 id = 20 這條記錄的主鍵索引上,加了范圍為 (15, 20] 的 next-key 鎖,意味著其他事務(wù)即無法更新或者刪除 id = 20 的記錄,同時(shí)無法插入 id 值為 16、17、18、19 的這一些新記錄。
  • 在特殊記錄( supremum pseudo-record)的主鍵索引上,加了范圍為 (20, +∞] 的 next-key 鎖,意味著其他事務(wù)無法插入 id 值大于 20 的這一些新記錄。

實(shí)驗(yàn)二:針對「大于等于」的范圍查詢的情況。

假設(shè)事務(wù) A 執(zhí)行了這條范圍查詢語句:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id >= 15 for update;
+----+-----------+-----+
| id | name | age |
+----+-----------+-----+
| 15 | 烏索普 | 20 |
| 20 | 香克斯 | 39 |
+----+-----------+-----+
2 rows in set (0.00 sec)

事務(wù) A 加鎖變化過程如下:

最開始要找的第一行是 id = 15,由于查詢該記錄是一個(gè)等值查詢(等于 15),所以該主鍵索引的 next-key 鎖會退化成記錄鎖,也就是僅鎖住 id = 15 這一行記錄。

由于是范圍查找,就會繼續(xù)往后找存在的記錄,掃描到的第二行是 id = 20,于是對該主鍵索引加的是范圍為  (15, 20] 的 next-key 鎖;

接著掃描到第三行的時(shí)候,掃描到了特殊記錄( supremum pseudo-record),于是對該主鍵索引加的是范圍為  (20, +∞] 的 next-key 鎖。

停止掃描。

可以得知,事務(wù) A 在主鍵索引上加了三個(gè) X 型 的鎖,分別是:

圖片

  • 在 id = 15 這條記錄的主鍵索引上,加了記錄鎖,范圍是 id = 15 這一行記錄;意味著其他事務(wù)無法更新或者刪除 id = 15 的這一條記錄;
  • 在 id = 20 這條記錄的主鍵索引上,加了 next-key 鎖,范圍是  (15, 20] 。意味著其他事務(wù)即無法更新或者刪除 id = 20 的記錄,同時(shí)無法插入 id 值為 16、17、18、19 的這一些新記錄。
  • 在特殊記錄( supremum pseudo-record)的主鍵索引上,加了 next-key 鎖,范圍是  (20, +∞] 。意味著其他事務(wù)無法插入 id 值大于 20 的這一些新記錄。

我們也可以通過 select * from performance_schema.data_locks\G; 這條語句來看看事務(wù) A 加了什么鎖。

輸出結(jié)果如下,我這里只截取了行級鎖的內(nèi)容。

圖片

通過前面這個(gè)實(shí)驗(yàn),我們證明了:

針對「大于等于」條件的唯一索引范圍查詢的情況下, 如果條件值的記錄存在于表中,那么由于查詢該條件值的記錄是包含一個(gè)等值查詢的操作,所以該記錄的索引中的 next-key 鎖會退化成記錄鎖。

2、針對「小于或者小于等于」的范圍查詢

實(shí)驗(yàn)一:針對「小于」的范圍查詢時(shí),查詢條件值的記錄「不存在」表中的情況。

假設(shè)事務(wù) A 執(zhí)行了這條范圍查詢語句,注意查詢條件值的記錄(id 為 6)并不存在于表中。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id < 6 for update;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 1 | 路飛 | 19 |
| 5 | 索隆 | 21 |
+----+--------+-----+
3 rows in set (0.00 sec)

事務(wù) A 加鎖變化過程如下:

  • 最開始要找的第一行是 id = 1,于是對該主鍵索引加的是范圍為  (-∞, 1] 的 next-key 鎖;
  • 由于是范圍查找,就會繼續(xù)往后找存在的記錄,掃描到的第二行是 id = 5,所以對該主鍵索引加的是范圍為  (1, 5] 的 next-key 鎖;
  • 由于掃描到的第二行記錄(id = 5),滿足 id < 6 條件,而且也沒有達(dá)到終止掃描的條件,接著會繼續(xù)掃描。
  • 掃描到的第三行是 id = 10,該記錄不滿足 id < 6 條件的記錄,所以 id = 10 這一行記錄的鎖會退化成間隙鎖,于是對該主鍵索引加的是范圍為  (5, 10) 的間隙鎖。
  • 由于掃描到的第三行記錄(id = 10),不滿足 id < 6 條件,達(dá)到了終止掃描的條件,于是停止掃描。

從上面的分析中,可以得知事務(wù) A 在主鍵索引上加了三個(gè) X 型的鎖:

圖片

  • 在 id = 1 這條記錄的主鍵索引上,加了范圍為  (-∞, 1] 的 next-key 鎖,意味著其他事務(wù)即無法更新或者刪除 id = 1 的這一條記錄,同時(shí)也無法插入 id 小于 1 的這一些新記錄。
  • 在 id = 5 這條記錄的主鍵索引上,加了范圍為  (1, 5] 的 next-key 鎖,意味著其他事務(wù)即無法更新或者刪除 id = 5 的這一條記錄,同時(shí)也無法插入 id 值為 2、3、4 的這一些新記錄。
  • 在 id = 10 這條記錄的主鍵索引上,加了范圍為 (5, 10) 的間隙鎖,意味著其他事務(wù)無法插入 id 值為 6、7、8、9 的這一些新記錄。

我們也可以通過 select * from performance_schema.data_locks\G; 這條語句來看看事務(wù) A 加了什么鎖。

輸出結(jié)果如下,我這里只截取了行級鎖的內(nèi)容。

圖片

從上圖中的分析中,也可以得知事務(wù) A 在主鍵索引加的三個(gè)鎖,就是我們前面分析出那三個(gè)鎖。

雖然這次范圍查詢的條件是「小于」,但是查詢條件值的記錄不存在于表中( id 為 6 的記錄不在表中),所以如果事務(wù) A 的范圍查詢的條件改成 <= 6 的話,加的鎖還是和范圍查詢條件為 < 6 是一樣的。 大家自己也驗(yàn)證下這個(gè)結(jié)論。

因此,針對「小于或者小于等于」的唯一索引范圍查詢,如果條件值的記錄不在表中,那么不管是「小于」還是「小于等于」的范圍查詢,掃描到終止范圍查詢的記錄時(shí),該記錄中索引的 next-key 鎖會退化成間隙鎖,其他掃描的記錄,則是在這些記錄的索引上加 next-key 鎖。

實(shí)驗(yàn)二:針對「小于等于」的范圍查詢時(shí),查詢條件值的記錄「存在」表中的情況。

假設(shè)事務(wù) A 執(zhí)行了這條范圍查詢語句,注意查詢條件值的記錄(id 為 5)存在于表中。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id <= 5 for update;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 1 | 路飛 | 19 |
| 5 | 索隆 | 21 |
+----+--------+-----+
2 rows in set (0.00 sec)

事務(wù) A 加鎖變化過程如下:

最開始要找的第一行是 id = 1,于是對該記錄加的是范圍為  (-∞, 1] 的 next-key 鎖;

由于是范圍查找,就會繼續(xù)往后找存在的記錄,掃描到的第二行是 id = 5,于是對該記錄加的是范圍為  (1, 5] 的 next-key 鎖。

由于主鍵索引具有唯一性,不會存在兩個(gè) id = 5 的記錄,所以不會再繼續(xù)掃描,于是停止掃描。

從上面的分析中,可以得到事務(wù) A 在主鍵索引上加了 2 個(gè) X 型的鎖:

圖片

  • 在 id = 1 這條記錄的主鍵索引上,加了范圍為  (-∞, 1] 的 next-key 鎖。意味著其他事務(wù)即無法更新或者刪除 id = 1 的這一條記錄,同時(shí)也無法插入 id 小于 1 的這一些新記錄。
  • 在 id = 5 這條記錄的主鍵索引上,加了范圍為  (1, 5] 的 next-key 鎖。意味著其他事務(wù)即無法更新或者刪除 id = 5 的這一條記錄,同時(shí)也無法插入 id 值為 2、3、4 的這一些新記錄。

我們也可以通過 select * from performance_schema.data_locks\G; 這條語句來看看事務(wù) A 加了什么鎖。

輸出結(jié)果如下,我這里只截取了行級鎖的內(nèi)容。

圖片

從上圖中的分析中,可以得到事務(wù) A 在主鍵索引上加了兩個(gè) X 型 next-key 鎖,分別是:

在 id = 1 這條記錄的主鍵索引上,加了范圍為  (-∞, 1] 的 next-key 鎖;

在 id = 5 這條記錄的主鍵索引上,加了范圍為(1, 5 ] 的 next-key 鎖。

實(shí)驗(yàn)三:再來看針對「小于」的范圍查詢時(shí),查詢條件值的記錄「存在」表中的情況。

如果事務(wù) A 的查詢語句是小于的范圍查詢,且查詢條件值的記錄(id 為 5)存在于表中。

select * from user where id < 5 for update;

事務(wù) A 加鎖變化過程如下:

最開始要找的第一行是 id = 1,于是對該記錄加的是范圍為  (-∞, 1] 的 next-key 鎖;

由于是范圍查找,就會繼續(xù)往后找存在的記錄,掃描到的第二行是 id = 5,該記錄是第一條不滿足 id < 5 條件的記錄,于是**該記錄的鎖會退化為間隙鎖,鎖范圍是 (1,5)**。

由于找到了第一條不滿足 id < 5 條件的記錄,于是停止掃描。

可以得知,此時(shí)事務(wù) A 在主鍵索引上加了兩種 X 型鎖:

![](https://cdn.xiaolincoding.com/gh/xiaolincoder/mysql/行級鎖/唯一索引范圍查詢小于.drawio (1).png)

  • 在 id = 1 這條記錄的主鍵索引上,加了范圍為  (-∞, 1] 的 next-key 鎖,意味著其他事務(wù)即無法更新或者刪除 id = 1 的這一條記錄,同時(shí)也無法插入 id 小于 1 的這一些新記錄。
  • 在 id = 5 這條記錄的主鍵索引上,加了范圍為 (1,5) 的間隙鎖,意味著其他事務(wù)無法插入 id 值為 2、3、4 的這一些新記錄。

我們也可以通過 select * from performance_schema.data_locks\G; 這條語句來看看事務(wù) A 加了什么鎖。

輸出結(jié)果如下,我這里只截取了行級鎖的內(nèi)容。

圖片

從上圖中的分析中,可以得到事務(wù) A 在主鍵索引上加了 X 型的范圍為  (-∞, 1] 的 next-key 鎖,和 X 型的范圍為 (1, 5) 的間隙鎖。

因此,通過前面這三個(gè)實(shí)驗(yàn),可以得知。

在針對「小于或者小于等于」的唯一索引(主鍵索引)范圍查詢時(shí),存在這兩種情況會將索引的 next-key 鎖會退化成間隙鎖的:

  • 當(dāng)條件值的記錄「不在」表中時(shí),那么不管是「小于」還是「小于等于」條件的范圍查詢,掃描到終止范圍查詢的記錄時(shí),該記錄的主鍵索引中的 next-key 鎖會退化成間隙鎖,其他掃描到的記錄,都是在這些記錄的主鍵索引上加 next-key 鎖。
  • 當(dāng)條件值的記錄「在」表中時(shí):

如果是「小于」條件的范圍查詢,掃描到終止范圍查詢的記錄時(shí),該記錄的主鍵索引中的 next-key 鎖會退化成間隙鎖,其他掃描到的記錄,都是在這些記錄的主鍵索引上,加 next-key 鎖。

如果是「小于等于」條件的范圍查詢,掃描到終止范圍查詢的記錄時(shí),該記錄的主鍵索引中的 next-key 鎖「不會」退化成間隙鎖,其他掃描到的記錄,都是在這些記錄的主鍵索引上加 next-key 鎖。

非唯一索引等值查詢

當(dāng)我們用非唯一索引進(jìn)行等值查詢的時(shí)候,因?yàn)榇嬖趦蓚€(gè)索引,一個(gè)是主鍵索引,一個(gè)是非唯一索引(二級索引),所以在加鎖時(shí),同時(shí)會對這兩個(gè)索引都加鎖,但是對主鍵索引加鎖的時(shí)候,只有滿足查詢條件的記錄才會對它們的主鍵索引加鎖。

針對非唯一索引等值查詢時(shí),查詢的記錄存不存在,加鎖的規(guī)則也會不同:

當(dāng)查詢的記錄「存在」時(shí),由于不是唯一索引,所以肯定存在索引值相同的記錄,于是非唯一索引等值查詢的過程是一個(gè)掃描的過程,直到掃描到第一個(gè)不符合條件的二級索引記錄就停止掃描,然后在掃描的過程中,對掃描到的二級索引記錄加的是 next-key 鎖,而對于第一個(gè)不符合條件的二級索引記錄,該二級索引的  next-key 鎖會退化成間隙鎖。同時(shí),在符合查詢條件的記錄的主鍵索引上加記錄鎖。

當(dāng)查詢的記錄「不存在」時(shí),掃描到第一條不符合條件的二級索引記錄,該二級索引的  next-key 鎖會退化成間隙鎖。因?yàn)椴淮嬖跐M足查詢條件的記錄,所以不會對主鍵索引加鎖。

接下里用兩個(gè)實(shí)驗(yàn)來說明。

1、記錄存在的情況

實(shí)驗(yàn)一:針對非唯一索引等值查詢時(shí),查詢的值存在的情況。

假設(shè)事務(wù) A 對非唯一索引(age)進(jìn)行了等值查詢,且表中存在 age = 22 的記錄。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where age = 22 for update;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 10 | 山治 | 22 |
+----+--------+-----+
1 row in set (0.00 sec)

事務(wù) A 加鎖變化過程如下:

  • 由于不是唯一索引,所以肯定存在值相同的記錄,于是非唯一索引等值查詢的過程是一個(gè)掃描的過程,最開始要找的第一行是 age = 22,于是對該二級索引記錄加上范圍為 (21, 22] 的 next-key 鎖。同時(shí),因?yàn)?age = 22 符合查詢條件,于是對 age = 22 的記錄的主鍵索引加上記錄鎖,即對 id = 10 這一行加記錄鎖。
  • 接著繼續(xù)掃描,掃描到的第二行是 age = 39,該記錄是第一個(gè)不符合條件的二級索引記錄,所以該二級索引的  next-key 鎖會退化成間隙鎖,范圍是 (22, 39)。
  • 停止查詢。

可以看到,事務(wù) A 對主鍵索引和二級索引都加了 X 型的鎖:

圖片

主鍵索引:

  • 在 id = 10 這條記錄的主鍵索引上,加了記錄鎖,意味著其他事務(wù)無法更新或者刪除 id = 10 的這一行記錄。

二級索引(非唯一索引):

  • 在 age = 22 這條記錄的二級索引上,加了范圍為 (21, 22] 的 next-key 鎖,意味著其他事務(wù)無法更新或者刪除 age = 22 的這一些新記錄,不過對于插入 age = 22 記錄的語句,在一些情況是可以成功插入的,而一些情況則無法成功插入,具體哪些情況,會在后面說。
  • 在 age = 39 這條記錄的二級索引上,加了范圍 (22, 39) 的間隙鎖。意味著其他事務(wù)無法插入 age 值為 23、24、..... 、38 的這一些新記錄。不過對于插入 age = 39 記錄的語句,在一些情況是可以成功插入的,而一些情況則無法成功插入,具體哪些情況,會在后面說。
  • 我們也可以通過 select * from performance_schema.data_locks\G; 這條語句來看看事務(wù) A 加了什么鎖。

輸出結(jié)果如下,我這里只截取了行級鎖的內(nèi)容。

圖片

從上圖的分析,可以看到,事務(wù) A 不僅對二級索引(INDEX_NAME: index_age  )加了范圍為 (21, 22] 的 X 型 next-key 鎖和范圍為 (22, 39) 的 X 型間隙鎖,而且還對主鍵索引(INDEX_NAME: PRIMARY )加了X 型的記錄鎖,范圍是 id = 10 這一行記錄。

2、記錄不存在的情況

實(shí)驗(yàn)二:針對非唯一索引等值查詢時(shí),查詢的值不存在的情況。

假設(shè)事務(wù) A 對非唯一索引(age)進(jìn)行了等值查詢,且表中不存在 age = 25 的記錄。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where age = 25 for update;
Empty set (0.00 sec)

事務(wù) A 加鎖變化過程如下:

  • 定位到第一條不符合查詢條件的二級索引記錄,即掃描到 age = 39,于是該二級索引的  next-key 鎖會退化成間隙鎖,范圍是 (22, 39)。
  • 停止查詢

事務(wù) A 在 age = 39 記錄的二級索引上,加了 X 型的間隙鎖,范圍是  (22, 39)。意味著其他事務(wù)無法插入 age 值為 23、24、25、26、....、38 這些新記錄。不過對于插入 age = 39 記錄的語句,在一些情況是可以成功插入的,而一些情況則無法成功插入,具體哪些情況,會在后面說。

圖片

我們也可以通過 select * from performance_schema.data_locks\G; 這條語句來看看事務(wù) A 加了什么鎖。

輸出結(jié)果如下,我這里只截取了行級鎖的內(nèi)容。

圖片

從上圖的分析,可以看到,事務(wù) A 在 age = 39 記錄的二級索引上(INDEX_NAME: index_age  ),加了范圍為 (22, 39) 的 X 型間隙鎖。

此時(shí),如果有其他事務(wù)插入了 age 值為 23、24、25、26、....、38 這些新記錄,那么這些插入語句都會發(fā)生阻塞。不過對于插入 age = 39 記錄的語句,在一些情況是可以成功插入的,而一些情況則無法成功插入,具體哪些情況,接下來我們就說!

當(dāng)有一個(gè)事務(wù)持有間隙鎖 (22, 39) 時(shí),到底是什么情況下,可以讓其他事務(wù)的插入  age = 39 記錄的語句成功?又是什么情況下,插入 age = 39 記錄時(shí)的語句會被阻塞?

我們先要清楚,什么情況下插入語句會發(fā)生阻塞。

插入語句在插入一條記錄之前,需要先定位到該記錄在 B+樹 的位置,如果插入的位置的下一條記錄的索引上有間隙鎖,才會發(fā)生阻塞。

在分析二級索引的間隙鎖是否可以成功插入記錄時(shí),我們要先要知道二級索引樹是如何存放記錄的?

二級索引樹是按照二級索引值(age列)按順序存放的,在相同的二級索引值情況下, 再按主鍵 id 的順序存放。知道了這個(gè)前提,我們才能知道執(zhí)行插入語句的時(shí)候,插入的位置的下一條記錄是誰。

事務(wù) A 是在 age = 39 記錄的二級索引上,加了 X 型的間隙鎖,范圍是  (22, 39)。插入 age = 39 記錄的成功和失敗的情況分別如下:

  • 當(dāng)其他事務(wù)插入一條 age = 39,id = 3 的記錄的時(shí)候,在二級索引樹上定位到插入的位置,而該位置的下一條是 id = 20、age = 39 的記錄,正好該記錄的二級索引上有間隙鎖,所以這條插入語句會被阻塞,無法插入成功。
  • 當(dāng)其他事務(wù)插入一條 age = 39,id = 21 的記錄的時(shí)候,在二級索引樹上定位到插入的位置,而該位置的下一條記錄不存在,也就沒有間隙鎖了,所以這條插入語句可以插入成功。

所以,插入 age = 39 記錄的語句是否可以插入成功,關(guān)鍵是要看插入 age = 39 記錄的時(shí)候,插入的位置的下一條記錄是否有間隙鎖,如果有間隙鎖,就會發(fā)生阻塞,如果沒有間隙鎖,則可以插入成功。

非唯一索引范圍查詢

非唯一索引和主鍵索引的范圍查詢的加鎖也有所不同,不同之處在于非唯一索引范圍查詢,索引的 next-key lock 不會有退化為間隙鎖和記錄鎖的情況,也就是非唯一索引進(jìn)行范圍查詢時(shí),對二級索引記錄加鎖都是加 next-key 鎖。

就帶大家簡單分析一下,事務(wù) A 的這條范圍查詢語句:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where age >= 22 for update;
+----+-----------+-----+
| id | name | age |
+----+-----------+-----+
| 10 | 山治 | 22 |
| 20 | 香克斯 | 39 |
+----+-----------+-----+
2 rows in set (0.01 sec)

事務(wù) A 的加鎖變化:

  • 最開始要找的第一行是 age = 22,雖然范圍查詢語句包含等值查詢,但是這里不是唯一索引范圍查詢,所以是不會發(fā)生退化鎖的現(xiàn)象,因此對該二級索引記錄加 next-key 鎖,范圍是 (21, 22]。同時(shí),對 age = 10 這條記錄的主鍵索引加記錄鎖,即對 id = 10 這一行記錄的主鍵索引加記錄鎖。
  • 由于是范圍查詢,接著繼續(xù)掃描已經(jīng)存在的二級索引記錄。掃面的第二行是 age = 39 的二級索引記錄,于是對該二級索引記錄加 next-key 鎖,范圍是 (22, 39],同時(shí),對 age = 39 這條記錄的主鍵索引加記錄鎖,即對 id = 20 這一行記錄的主鍵索引加記錄鎖。
  • 雖然我們看見表中最后一條二級索引記錄是 age = 39 的記錄,但是實(shí)際在 Innodb 存儲引擎中,會用一個(gè)特殊的記錄來標(biāo)識最后一條記錄,該特殊的記錄的名字叫 supremum pseudo-record ,所以掃描第二行的時(shí)候,也就掃描到了這個(gè)特殊記錄的時(shí)候,會對該二級索引記錄加的是范圍為  (39, +∞] 的 next-key 鎖。
  • 停止查詢

可以看到,事務(wù) A 對主鍵索引和二級索引都加了 X 型的鎖:

圖片

  • 主鍵索引(id 列):

在 id = 10 這條記錄的主鍵索引上,加了記錄鎖,意味著其他事務(wù)無法更新或者刪除 id = 10 的這一行記錄。

在 id = 20 這條記錄的主鍵索引上,加了記錄鎖,意味著其他事務(wù)無法更新或者刪除 id = 20 的這一行記錄。

  • 二級索引(age 列):

在 age = 22 這條記錄的二級索引上,加了范圍為 (21, 22] 的 next-key 鎖,意味著其他事務(wù)無法更新或者刪除 age = 22 的這一些新記錄,不過對于插入 age = 22 記錄的語句,在一些情況是可以成功插入的,而一些情況則無法成功插入,具體哪些情況,我們前面也講了。

在 age = 39 這條記錄的二級索引上,加了范圍為 (22, 39] 的 next-key 鎖,意味著其他事務(wù)無法更新或者刪除 age = 39 的這一些記錄,也無法插入 age 值為 23、24、25、...、38 的這一些新記錄。不過對于插入 age = 22 記錄的語句是,在一些情況是可以成功插入的,而一些情況則無法成功插入,具體哪些情況,我們前面也講了。

在特殊的記錄(supremum pseudo-record)的二級索引上,加了范圍為 (39, +∞] 的 next-key 鎖,意味著其他事務(wù)無法插入 age 值大于 39 的這些新記錄。

沒有加索引的查詢

前面的案例,我們的查詢語句都有使用索引查詢,也就是查詢記錄的時(shí)候,是通過索引掃描的方式查詢的,然后對掃描出來的記錄進(jìn)行加鎖。

如果鎖定讀查詢語句,沒有使用索引列作為查詢條件,或者查詢語句沒有走索引查詢,導(dǎo)致掃描是全表掃描。那么,每一條記錄的索引上都會加 next-key 鎖,這樣就相當(dāng)于鎖住的全表,這時(shí)如果其他事務(wù)對該表進(jìn)行增、刪、改操作的時(shí)候,都會被阻塞。

不只是鎖定讀查詢語句不加索引才會導(dǎo)致這種情況,update 和 delete 語句如果查詢條件不加索引,那么由于掃描的方式是全表掃描,于是就會對每一條記錄的索引上都會加 next-key 鎖,這樣就相當(dāng)于鎖住的全表。

因此,在線上在執(zhí)行 update、delete、select ... for update 等具有加鎖性質(zhì)的語句,一定要檢查語句是否走了索引,如果是全表掃描的話,會對每一個(gè)索引加 next-key 鎖,相當(dāng)于把整個(gè)表鎖住了,這是挺嚴(yán)重的問題。

總結(jié)

這次我以 MySQL 8.0.26 版本,在可重復(fù)讀隔離級別之下,做了幾個(gè)實(shí)驗(yàn),讓大家了解了唯一索引和非唯一索引的行級鎖的加鎖規(guī)則。

我這里總結(jié)下,  MySQL 行級鎖的加鎖規(guī)則。

唯一索引等值查詢:

  • 當(dāng)查詢的記錄是「存在」的,在索引樹上定位到這一條記錄后,將該記錄的索引中的 next-key lock 會退化成「記錄鎖」。
  • 當(dāng)查詢的記錄是「不存在」的,則會在索引樹找到第一條大于該查詢記錄的記錄,然后將該記錄的索引中的 next-key lock 會退化成「間隙鎖」。

非唯一索引等值查詢:

  • 當(dāng)查詢的記錄「存在」時(shí),由于不是唯一索引,所以肯定存在索引值相同的記錄,于是非唯一索引等值查詢的過程是一個(gè)掃描的過程,直到掃描到第一個(gè)不符合條件的二級索引記錄就停止掃描,然后在掃描的過程中,對掃描到的二級索引記錄加的是 next-key 鎖,而對于第一個(gè)不符合條件的二級索引記錄,該二級索引的 next-key 鎖會退化成間隙鎖。同時(shí),在符合查詢條件的記錄的主鍵索引上加記錄鎖。
  • 當(dāng)查詢的記錄「不存在」時(shí),掃描到第一條不符合條件的二級索引記錄,該二級索引的  next-key 鎖會退化成間隙鎖。因?yàn)椴淮嬖跐M足查詢條件的記錄,所以不會對主鍵索引加鎖。

非唯一索引和主鍵索引的范圍查詢的加鎖規(guī)則不同之處在于:

  • 唯一索引在滿足一些條件的時(shí)候,索引的 next-key lock 退化為間隙鎖或者記錄鎖。
  • 非唯一索引范圍查詢,索引的 next-key lock 不會退化為間隙鎖和記錄鎖。

還有一件很重要的事情,在線上在執(zhí)行 update、delete、select ... for update 等具有加鎖性質(zhì)的語句,一定要檢查語句是否走了索引,如果是全表掃描的話,會對每一個(gè)索引加 next-key 鎖,相當(dāng)于把整個(gè)表鎖住了,這是挺嚴(yán)重的問題。

就說到這啦, 我們下次見啦!

責(zé)任編輯:武曉燕 來源: 小林coding
相關(guān)推薦

2020-03-02 15:17:37

云原生CNCF容器

2019-07-04 09:13:04

中臺百度團(tuán)隊(duì)

2021-02-25 08:21:38

高可用風(fēng)險(xiǎn)故障

2020-10-29 10:35:53

Nginx架構(gòu)服務(wù)器

2019-10-21 08:51:41

分布式事務(wù)CAPAP

2018-07-26 09:06:29

Java內(nèi)存模型

2020-01-13 15:34:10

超融合邊緣計(jì)算架構(gòu)

2019-02-21 16:24:28

5G火車站設(shè)備

2025-06-23 08:20:00

PaimonFluss大數(shù)據(jù)

2019-09-26 09:24:01

GC原理調(diào)優(yōu)

2024-12-13 08:06:38

Java分類理

2024-09-23 05:10:00

微服務(wù)CORSSpringBoot

2019-11-23 17:27:54

IO開源

2018-11-28 11:08:30

并查集集合數(shù)據(jù)結(jié)構(gòu)

2022-07-21 21:19:48

元宇宙

2023-10-27 15:31:04

For循環(huán)Foreach循環(huán)

2021-02-11 08:08:09

Spring Boot配置架構(gòu)

2023-01-26 01:09:31

配置數(shù)據(jù)源參數(shù)

2019-12-06 09:16:23

Linux 開源操作系統(tǒng)

2025-05-23 10:00:00

網(wǎng)絡(luò)交換機(jī)STP
點(diǎn)贊
收藏

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

亚洲九九九在线观看| 亚洲精品国产无天堂网2021 | 在线观看www91| 亚洲ai欧洲av| 亚洲第一页在线观看| 1024成人| 日韩中文字幕视频在线| 性农村xxxxx小树林| 日韩网站中文字幕| 亚洲老妇xxxxxx| 免费av一区二区三区| 一级淫片免费看| 欧美久久九九| 国产亚洲美女久久| 亚洲av午夜精品一区二区三区| 在线看片国产福利你懂的| 国产精品亲子乱子伦xxxx裸| 国产一区国产精品| 国产精品无码白浆高潮| 亚洲女同在线| 久久久久久国产精品美女| 日本少妇xxxxx| 动漫3d精品一区二区三区乱码| 欧美三区在线视频| 日本福利视频在线| av中文字幕在线观看| 国产视频亚洲色图| 国产视频精品网| 国产精品爽爽久久久久久| 首页综合国产亚洲丝袜| 97在线视频观看| 国产波霸爆乳一区二区| 99久久精品费精品国产| 亚洲人成网在线播放| 成人免费看片载| 激情综合五月| 欧美精品一卡两卡| 日本三区在线观看| 中文字幕在线视频久| 亚洲成人免费看| 国产高清不卡无码视频| 国产调教视频在线观看| 国产精品久久久久久久久免费相片 | 色偷偷av一区二区三区| 国产亚洲精品熟女国产成人| 任你躁在线精品免费| 欧美精品一区二区三区蜜桃| 视频区 图片区 小说区| 日韩三级一区| 欧美另类videos死尸| 欧美大尺度做爰床戏| 国产精品高清乱码在线观看| 色综合久久久久| 成人免费毛片网| 欧美少妇精品| 欧美日韩在线看| 日韩黄色片视频| 末成年女av片一区二区下载| 婷婷丁香激情综合| 国产99久久九九精品无码| 黄页网站在线| 亚洲国产一二三| 国产老熟妇精品观看| 午夜影院在线播放| 欧美性xxxx在线播放| 日本熟妇人妻xxxxx| 亚洲不卡系列| 欧美乱熟臀69xxxxxx| 亚洲天堂av一区二区三区| 日韩最新av| 亚洲精品一区二区三区香蕉| 中国黄色a级片| 精品国产中文字幕第一页| 最新的欧美黄色| 欧产日产国产v| 极品中文字幕一区| 欧美一级免费视频| 欧美另类高清videos的特点| 九九视频精品免费| 成人免费视频观看视频| 亚洲日本国产精品| 欧美激情一区二区三区蜜桃视频 | 国产高清精品一区| 午夜激情小视频| 国产精品欧美精品| 国产人妻人伦精品| 亚洲女色av| 欧美久久久久久蜜桃| 中文字幕永久免费| 女优一区二区三区| 久久久成人的性感天堂| 日韩aaaaaa| 日韩极品在线观看| 91在线精品观看| 日本v片在线免费观看| 国产精品久久久久久久蜜臀| 成年人深夜视频| 666av成人影院在线观看| 欧美一区二区美女| 搡老熟女老女人一区二区| 手机亚洲手机国产手机日韩| 久久久久久综合网天天| 中文天堂在线资源| 成人av午夜影院| 亚洲视频在线二区| 色老头在线一区二区三区| 欧美日本一区二区三区| 少妇一级淫片免费放播放| 日韩视频在线观看| 69影院欧美专区视频| 国产免费av电影| 久久久亚洲欧洲日产国码αv| 天堂av免费看| 欧美国产日韩电影| 亚洲精品美女在线观看| 一区视频免费观看| 蜜臀久久99精品久久久久久9| 国产精品有限公司| 欧美性猛交xxx乱大交3蜜桃| 精品久久久久久国产| 欧美污在线观看| 久久中文视频| 国产经典一区二区| 五月天婷婷在线观看| 一区二区三区久久| 亚洲精品视频三区| 欧美老女人另类| 青青a在线精品免费观看| 亚洲欧美高清视频| 自拍偷自拍亚洲精品播放| 黄色一级大片在线观看| 欧美理伦片在线播放| 欧美高清视频在线播放| 国产女人18毛片水18精| 亚洲欧洲日韩综合一区二区| 久久精品免费网站| 国产一区二区三区四区二区| 欧美一区二区视频97| 天堂网在线中文| 夜夜爽夜夜爽精品视频| 久久精品一卡二卡| 99国产精品一区二区| 国产精品视频一区二区三区四 | 三级小说欧洲区亚洲区| 久久久久久18| 天天干天天做天天操| 亚洲成人免费看| 亚洲国产精品无码久久久久高潮| 国内激情久久| 国产伦精品一区二区三区视频黑人| 在线观看三级视频| 欧美v国产在线一区二区三区| 午夜写真片福利电影网| 国产老肥熟一区二区三区| 国产精品12p| 欧美电影在线观看一区| 久久久精品国产| 国产三级伦理片| 亚洲精品一二三区| 成人免费看片载| 国产精品久久久久久久免费软件 | 性生活在线视频| 欧美日本不卡高清| 精品人伦一区二区三区| 中文字幕在线视频网站| 一区二区三区视频免费| 在线视频1卡二卡三卡| 亚洲日本一区二区| 精品国产免费久久久久久婷婷| 激情一区二区| 欧美高清性xxxxhd| 欧美jizz18| 欧美疯狂做受xxxx高潮| 午夜视频在线播放| 在线精品视频一区二区三四| 69夜色精品国产69乱| 国产91露脸合集magnet| 精品国产免费av| 欧美日韩国产一区二区三区不卡| 91精品久久久久久久久久另类| 影音先锋男人资源在线| 亚洲精品色婷婷福利天堂| 国产情侣小视频| 亚洲男人天堂av| 五月开心播播网| 麻豆精品一区二区| 大伊香蕉精品视频在线| 国产精品日韩精品中文字幕| 91亚洲精华国产精华| sm性调教片在线观看| 中文字幕久久久av一区| 亚洲av无码片一区二区三区| 欧美日韩亚洲天堂| 成人做爰视频网站| 99精品久久只有精品| 手机免费av片| 日韩一级不卡| 亚洲欧洲精品一区二区三区波多野1战4 | 欧美 日韩 国产一区二区在线视频| 国产偷国产偷亚洲高清97cao| 一区在线影院| 97超碰国产精品女人人人爽| 日韩美女网站| 日韩av影视在线| 国产手机av在线| 一本大道av一区二区在线播放| 成人性生活毛片| 久久亚洲一级片| 4438x全国最大成人| 日韩av中文字幕一区二区三区| 国产在线视频在线| 99精品在线观看| 欧洲精品亚洲精品| 超碰精品在线观看| 91人人爽人人爽人人精88v| 成人影院大全| 久久久噜噜噜久久久| 蜜桃视频网站在线| 国产亚洲精品美女| 香港三日本三级少妇66| 日韩欧美成人激情| 91精品国产乱码久久久久| 色婷婷精品大视频在线蜜桃视频| 久久久久久久久精| 一区在线中文字幕| 中文国语毛片高清视频| 久久精品视频一区二区三区| 99久久国产精| 99久久伊人精品| 在线观看一区二区三区视频| 精品一区免费av| 91制片厂毛片| 日本aⅴ精品一区二区三区| 777久久久精品一区二区三区| 欧美日韩福利| 国产精品视频网站在线观看 | 青青艹视频在线| 亚洲每日在线| 国产精品又粗又长| 亚洲黄色视屏| 无罩大乳的熟妇正在播放| 91久久亚洲| 日韩精品―中文字幕| 亚洲激情专区| av7777777| 久久不射网站| 北条麻妃在线视频| 视频一区欧美精品| 欧美视频第三页| 日韩专区一卡二卡| 亚洲色图 在线视频| 免费观看成人鲁鲁鲁鲁鲁视频| 欧美日韩在线免费播放| 日本成人中文字幕| 欧美美女性视频| 国模大尺度一区二区三区| 青娱乐精品在线| 国产69精品久久99不卡| 中文字幕乱码一区| 久久这里只有精品首页| www.av天天| 国产精品久久免费看| 尤物在线免费视频| 亚洲永久免费av| 久草国产精品视频| 色婷婷综合久久久久中文一区二区 | 中日韩视频在线观看| 欧美日韩亚洲一| 免费观看30秒视频久久| 在线成人精品视频| 99re66热这里只有精品3直播| 国产精品无码久久久久一区二区| 国产欧美精品国产国产专区| 99久久久免费精品| 亚洲午夜久久久| 亚洲熟女综合色一区二区三区| 在线观看成人免费视频| 国产福利小视频| 亚洲国产三级网| 91社区在线观看| 欧美精品电影免费在线观看| 成人性生交大片免费观看网站| 成人激情视频免费在线| 黑色丝袜福利片av久久| 日韩欧美一区二区视频在线播放| 911久久香蕉国产线看观看| 欧美一级片免费播放| 日产欧产美韩系列久久99| 青娱乐精品在线| 久久久久久久久久久久久久久99 | 中文字幕日韩一区二区不卡| 成人午夜精品久久久久久久蜜臀| 视频一区国产视频| 成人在线观看一区二区| 欧美激情在线观看视频免费| 妺妺窝人体色www婷婷| 欧美综合在线视频| 亚洲欧美高清视频| 最新国产精品亚洲| 婷婷电影在线观看| 亚洲资源在线看| 成人91在线| 99精品人妻少妇一区二区| 国产呦精品一区二区三区网站| 野花社区视频在线观看| 亚洲另类在线制服丝袜| 国产无遮挡又黄又爽又色视频| 欧美一区二区精品| 国产永久av在线| 久久男人av资源网站| 高清一区二区三区av| 日韩偷拍一区二区| 国产日韩欧美一区在线| 亚洲在线观看网站| 国产日韩成人精品| 精品成人久久久| 日韩欧美国产午夜精品| 免费高清在线观看| 国产精品第一视频| 日韩av午夜| 欧美国产日韩激情| 国产黄人亚洲片| 国产探花在线视频| 欧美亚一区二区| 男人天堂资源在线| 91av成人在线| 国产精品网在线观看| av中文字幕av| 韩国v欧美v日本v亚洲v| 欧美日韩中文字幕视频| 色哟哟亚洲精品| 蝌蚪视频在线播放| 欧洲精品在线视频| 香蕉久久精品| 激情综合在线观看| jvid福利写真一区二区三区| 久久久久无码精品国产| 欧美一区二区三区四区在线观看| 免费黄网站在线播放| 国产日韩欧美在线看| 欧美gayvideo| 91精品999| 最新久久zyz资源站| 96亚洲精品久久久蜜桃| 精品国产一区二区三区四区在线观看 | 欧美男生操女生| 日本在线人成| 91欧美日韩一区| 欧美激情日韩| www.555国产精品免费| 亚洲一区二区精品视频| www.国产精品视频| 色在人av网站天堂精品| 亚洲无线观看| 国产h视频在线播放| 91免费观看视频| 久久久黄色大片| 在线观看亚洲视频| 99久久999| 国产精品视频二| av欧美精品.com| 国内自拍视频在线播放| 国产一区二区三区三区在线观看 | 欧美一区二区国产| 性欧美猛交videos| 国产欧美日韩一区二区三区| 亚洲一区日本| avhd101老司机| 日韩一区二区三区在线视频| 国产一线二线在线观看| 久久99精品国产99久久| 水野朝阳av一区二区三区| 四虎地址8848| 欧美videossexotv100| 神马午夜在线视频| 水蜜桃一区二区三区| 国产乱色国产精品免费视频| 国产午夜视频在线播放| 亚洲欧洲在线播放| vam成人资源在线观看| www.国产在线视频| 久久久久久电影| 一级做a爰片久久毛片16| 欧美激情一区二区三级高清视频| 日韩av网站在线免费观看| 成人日韩在线视频| 亚洲国产精品一区二区久久| 黄色小视频在线免费观看| 亚洲精品欧美一区二区三区| 中文日韩在线| 久久精品日韩无码| 日韩电影中文字幕在线| 日韩精品第二页| 91九色在线观看视频| 中文字幕一区免费在线观看| 婷婷av一区二区三区| 91精品在线影院| 欧美一级专区| 久久国产精品波多野结衣av| 在线精品高清中文字幕|