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

為什么 MySQL 插入不存在的數據會產生死鎖?

數據庫 MySQL
我們簡單說一下。MySQL 中,我們現在用的引擎幾乎都是 innodb,我們以此為默認條件展開話題。常見的鎖有兩種,一種是表鎖,一種是行鎖。當你更新一條數據時,如果你不走索引,那會鎖表,否則,鎖行。

mysql 的讀寫鎖這個話題,老難了。

水太深,容易把握不住。

看書吧犯困。

但面試又繞不過去。

今天我們面向一個例子學習,希望能讓大家提起一點興趣。

直接開始吧。

有這么一張表,建表 sql 如下。

CREATE TABLE `user` (  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',  `name` varchar(100) NOT NULL DEFAULT '' COMMENT '名字',  `phone_no` int(10) NOT NULL DEFAULT '0' COMMENT '電話號碼',  PRIMARY KEY (`id`),  KEY `idx_phone_no` (`phone_no`)) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

這里需要關注的是,電話號碼這一列,是加了普通索引的。

mysql 有四層隔離級別,應對不同的事務并發(fā)處理能力。

假設我們現在用的是可重復讀隔離級別。

當前數據表長這樣。

注意里面沒有 phone_no=2。

現在代碼里有這么一段邏輯。

select user where phone_no=2 for update  // 查詢sqlif (user 存在) {        return} else {  insert user;   // 插入sql}

邏輯比較簡單,就是去查一下 phone_no=2 的數據存不存在。不存在的話,就插入一條到數據庫里。

目的是保證 phnotallow=2 的數據唯一。

注意跟平時的 select 查詢不一樣,select 語句后面還有個 for update ,是為了對 phone_no=2 進行加鎖,不懂沒關系,待會還會提到。

現在有兩個線程同時并發(fā)跑上面的邏輯。

結果竟然提示死鎖。

Deadlock found when trying to get lock; try restarting transaction

為什么呢?

為了解釋這個問題,我們就從 mysql 鎖的話題開始聊起。

mysql 鎖的話題很大了,這個細細聊起來又是一篇長文了。

我們簡單說一下。mysql 中,我們現在用的引擎幾乎都是 innodb,我們以此為默認條件展開話題。

常見的鎖有兩種,一種是表鎖,一種是行鎖。

當你更新一條數據時,如果你不走索引,那會鎖表,否則,鎖行。

表鎖是在你什么索引都沒有的時候,為了保證數據一致性,迫不得已加上去的,鎖的對象當然就是整個數據表了。也就是說,你在對數據表加鎖之后,再對表進行讀寫操作,結束之后對表解鎖。在此期間,其他對這張表的寫操作都得等你操作完。

是的,干等,哪怕你操作的是第 1 行的數據,其他人要操作的是第 100 行的數據,也得干等。

為了提升效率,設計 mysql 的大佬們又把鎖的粒度給減小了,粒度從鎖表變成鎖行。

也就是說,你先鎖住這個行,在讀寫完這一行之后,再解鎖,期間其他人如果要操作這一行,那要等著,如果操作其他行,那就不用等了。這樣并發(fā)量就提上去了。

而加鎖除了 update, insert 這類寫類型的語句會加之外,還可以在 select 語句的最后加入for update,這樣也能加鎖。比如:

select * from user where phone_no =2 for update;

update 語句會加鎖比較好理解,就是你要更新某一行了,防止別人這時候也要更新,所以加鎖。后者 select for update 就是告訴別人,你讀的這一行接下來是要拿來做更新的操作的(for update),在你更新完成前,誰也不能更新它。

上面這種鎖,主要是為了寫的時候加入的,叫寫鎖,也就是 X 鎖。

寫鎖跟寫鎖之間是互斥的。意思是不能同時對某一行加兩個寫鎖,凡事講究先來后到,后面加寫鎖的線程會阻塞等待前面的線程解鎖完。

既然有寫鎖,當然有讀鎖,也叫 S 鎖。

像下面這樣在 select 語句后面加上lock in share mode,就能加入讀鎖。

select * from user where phone_no =2 lock in share mode;

讀鎖和讀鎖之間就不互斥,也就是兩個線程可以對同一行數據同時加讀鎖,不會阻塞。

死鎖

簡單解釋下死鎖,一個數據表里有那么多行,我們寫代碼的時候,會執(zhí)行各種 sql 語句,期間完全可以鎖住多行。

當一個線程先鎖 A 行,再鎖 B 行時,另外一個線程反過來,先鎖 B 行,再鎖 A 行。就有可能發(fā)生兩個線程在已經持有一個鎖的同時,死等對方持有的另外一個鎖釋放的情況。

雙方都想拿對方的鎖,且自己的鎖也死死不松手,邏輯就都跑不下去了,這就是死鎖。

間隙鎖

那么我們回到文章開頭的話題上。

如果我能保證,對 id=2 的那一行加鎖,寫結束前都不釋放,期間別人都沒法寫,這樣豈不是保證數據唯一了?

道理是這么個道理沒錯,但是現在的關鍵是,phone_no=2 這一行并不存在。

select user where phone_no=2 for update

這一行 sql 一執(zhí)行,牢牢鎖住了空氣?

開個玩笑。

是不是什么也沒鎖住,這個要看隔離級別了。

phone_no 是加了索引的,且因為數據庫索引里,數據是排好序的,phone_no=1 和 phone_no=3 都存在,他們之間沒有數據,如果有 phone_no=2 這條數據的話,那也理應出現在他們中間。

那么現在的問題是,有沒有辦法鎖住 1 和 3 之間的縫隙?

有的,有個間隙鎖,這個鎖,在讀未提交和讀已提交里都沒有,它在可重復讀這個隔離級別下被引入。

而且,間隙鎖和間隙鎖之間是不互斥的。

記住上面這句話,老關鍵了。

于是乎,我們回到文章開頭的問題里,這次我加上注釋。

線程 1在可重復讀這個隔離級別下,通過 for update ,可以在 1 和 3 之間,加上間隙鎖。

線程 2 也一樣,也在 1 和 3 之間加上間隙鎖,因為間隙鎖和間隙鎖之間是不互斥的,所以也能加鎖成功。

這時候線程 1 嘗試去插入數據,插入數據的時候也會加一個特殊的鎖,專業(yè)點,叫插入意向鎖。插入意向鎖跟間隙鎖是互斥的。

但由于線程 2 前面已經加過間隙鎖了。所以線程 1 會等線程 2 釋放間隙鎖。

但線程 2,不僅不釋放間隙鎖,反而又打算加一個寫鎖。

哦吼。

相當于兩個線程在持有一個鎖的同時,還等著對方釋放鎖。

這就妥妥死鎖了。

這下,文章開頭死鎖的問題,就解釋完了。

那么問題又來了。

為什么可重復讀要引入間隙鎖?

可重復讀最關鍵的一個點是,我開了一個事務,在這個事務里,不管我讀多少次,我讀到的數據都要是一樣的,這才是可重復讀。如果 mysql 不存在間隙鎖,那么就有可能出現下面的情況。

在一個事務里,讀多次數據,發(fā)現每次數據都不同。就好像出現幻覺一樣,所以又叫幻讀。

這就跟可重復讀的定義違背了。

通過加入間隙鎖,線程 1 在第一次執(zhí)行 select for update 后,線程 2 如果再嘗試去寫數據,就會被阻塞,直到線程 1 執(zhí)行 commit 后,線程 2 阻塞結束然后執(zhí)行 insert。

可重復讀隔離級別下,通過引入間隙鎖,是為了解決幻讀的問題。

總結

  • ? mysql 鎖從粒度上分為行鎖和表鎖,從行為上又分為讀鎖和寫鎖,也就是 S 鎖和 X 鎖。
  • ? 兩個線程在持有鎖的同時,又想等待對方把鎖釋放掉,則會發(fā)生死鎖。
  • ? 兩個間隙鎖之間不會互斥。
  • ? 在可重復讀隔離級別下,通過間隙鎖解決了幻讀。

參考資料

《MYSQL 內核:INNODB 存儲引擎 卷 1》

最后

這篇文章只是想通過一個例子講講鎖的內容。并不是希望通過這樣的方式來保證并發(fā)寫入唯一數據。

如果只是想在并發(fā)寫時保證數據唯一的話,加個唯一索引吧,別搞上面這些花里胡哨的。

責任編輯:姜華 來源: 小白debug
相關推薦

2010-01-05 13:52:02

2022-01-18 06:59:50

HashMap循環(huán)底層

2022-05-18 08:25:59

MySQLutf8字符集數據庫

2018-07-03 14:20:10

數據庫恢復備份

2018-07-19 06:14:09

2024-01-19 21:55:57

C++編程代碼

2010-05-17 18:03:31

MySQL 數據庫

2017-12-26 08:25:57

硬盤數據丟失

2024-12-13 08:25:59

DML操作SQL

2022-03-23 10:01:56

黑匣子云端

2025-01-07 08:20:00

2021-01-25 07:21:24

GitHub 開源代碼下載

2023-09-12 08:02:13

viewport斷點

2024-07-22 08:03:55

2011-03-23 09:10:09

游標數據檢索

2020-11-03 10:23:22

云計算容器技術

2015-10-20 10:30:59

創(chuàng)業(yè)時機

2009-09-12 09:34:18

Windows 7中國售價

2011-08-24 09:45:34

Oracle數據庫

2019-12-31 09:11:01

后臺Android系統
點贊
收藏

51CTO技術棧公眾號