這篇文章為大家帶來了關於mysql中死鎖的相關問題,主要介紹了兩個一樣的INSERT語句引發死鎖的相關知識,希望對大家有幫助。
兩條一樣的INSERT語句竟然引發了死鎖,這究竟是人性的扭曲,還是道德的淪喪,讓我們不禁感嘆一句:臥槽!這也能死鎖,然後眼中含著悲催的淚水無奈的改起了業務代碼。
好的,在深入分析為啥兩條一樣的INSERT語句也會產生死鎖之前,我們先介紹一些基礎知識。
準備一下環境
為了故事的順利發展,我們新建一個用了無數次的hero表:
CREATE TABLE hero ( number INT AUTO_INCREMENT, name VARCHAR(100), country varchar(100), PRIMARY KEY (number), UNIQUE KEY uk_name (name) ) Engine=InnoDB CHARSET=utf8;
然後向這個表裡插入幾筆記錄:
INSERT INTO hero VALUES (1, 'l刘备', '蜀'), (3, 'z诸葛亮', '蜀'), (8, 'c曹操', '魏'), (15, 'x荀彧', '魏'), (20, 's孙权', '吴');
現在hero表就有了兩個索引(一個唯一二級索引,一個叢集索引),示意圖如下:
INSERT語句如何加鎖
讀過《MySQL是怎麼運作的:根兒上理解MySQL》的小夥伴一定知道,INSERT語句在正常執行時是不會產生鎖定結構的,它是靠叢集索引記錄自帶的trx_id隱藏列來作為隱式鎖來保護記錄的。
但是在一些特殊場景下,INSERT語句還是會產生鎖定結構的,我們列舉一下:
1. 待插入記錄的下一筆記錄上已經被其他交易加了gap鎖定時
每插入一筆新記錄,都需要看一下待插入記錄的下一筆記錄上是否已經被加了gap鎖,如果已加gap鎖,那INSERT語句應該被阻塞,產生一個插入意向鎖。
比方說對於hero表來說,事務T1運行在REPEATABLE READ(後續簡稱為RR,後續也會把READ COMMITTED簡稱為RC)隔離等級中,執行了下邊的語句:
# 事务T1 mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> SELECT * FROM hero WHERE number < 8 FOR UPDATE; +--------+------------+---------+ | number | name | country | +--------+------------+---------+ | 1 | l刘备 | 蜀 | | 3 | z诸葛亮 | 蜀 | +--------+------------+---------+ 2 rows in set (0.02 sec)
這條語句會對主鍵值為1、3、8的這3筆記錄都添加X型next-key鎖,不信的話我們使用SHOW ENGINE INNODB STATUS語句看一下加鎖情況,圖中箭頭指向的記錄就是number值為8的記錄:
小貼士:
至於SELECT、DELETE、UPDATE語句如何加鎖,我們已經在之前的文章中分析過了,這裡就不再贅述了。
此時事務T2想插入一條主鍵值為4的叢集索引記錄,那麼T2在插入記錄前,首先要定位一下主鍵值為4的叢集索引記錄在頁面中的位置,發現主鍵值為4的下一記錄的主鍵值是8,而主鍵值是8的聚簇索引記錄已經被添加了gap鎖(next-key鎖包含了正經記錄鎖和gap鎖),那麼事務1就需要進入阻塞狀態,並產生一個類型為插入意向鎖的鎖結構。
我們在交易T2中執行一下INSERT語句驗證一下:
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO hero VALUES(4, 'g关羽', '蜀');
此時T2進入阻塞狀態,我們再使用SHOW ENGINE INNODB STATUS看一下加鎖狀況:
可見T2對主鍵值為8的叢集索引記錄加上了插入意圖鎖定(就是箭頭處指向的lock_mode X locks gap before rec insert intention),並且處在waiting狀態。
好了,驗證過之後,我們再來看看程式碼裡是如何實現的:
lock_rec_insert_check_and_lock函數用來看別的交易是否阻止本次INSERT插入,如果是,那麼本事務就為被別的事務添加了gap鎖的記錄產生一個插入意向鎖,具體過程如下:
小秘訣:
lock_rec_other_has_conflicting函數用來偵測本次要取得的鎖和記錄上已有的鎖是否有衝突,有興趣的同學可以看一下。
2. 遇到重複鍵時
#如果在插入新記錄時,發現頁面中已有的記錄的主鍵或者唯一二級索引列與待插入記錄的主鍵或唯一二級索引列值相同(不過可以有多筆記錄的唯一二級索引列的值同時為NULL,這裡不考慮這種情況了) ,此時插入新記錄的交易會取得頁面中已存在的鍵值相同的記錄的鎖定。
如果是主鍵值重複,那麼:
當隔離等級不大於RC時,插入新記錄的交易會給已存在的主鍵值重複的叢集索引記錄新增S型正經記錄鎖定。
當隔離等級不小於RR時,插入新記錄的交易會為已存在的主鍵值重複的叢集索引記錄新增S型next-key鎖定。
如果是唯一二级索引列重复,那不论是哪个隔离级别,插入新记录的事务都会给已存在的二级索引列值重复的二级索引记录添加S型next-key锁,再强调一遍,加的是next-key锁!加的是next-key锁!加的是next-key锁!这是rc隔离级别中为数不多的给记录添加gap锁的场景。
小贴士:
本来设计InnoDB的大叔并不想在RC隔离级别引入gap锁,但是由于某些原因,如果不添加gap锁的话,会让唯一二级索引中出现多条唯一二级索引列值相同的记录,这就违背了UNIQUE约束。所以后来设计InnoDB的大叔就很不情愿的在RC隔离级别也引入了gap锁。
我们也来做一个实验,现在假设上边的T1和T2都回滚了,现在将隔离级别调至RC,重新开启事务进行测试。
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; Query OK, 0 rows affected (0.01 sec) # 事务T1 mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO hero VALUES(30, 'x荀彧', '魏'); ERROR 1062 (23000): Duplicate entry 'x荀彧' for key 'uk_name'
然后执行SHOW ENGINE INNODB STATUS语句看一下T1加了什么锁:
可以看到即使现在T1的隔离级别为RC,T1仍然给name列值为'x荀彧'的二级索引记录添加了S型next-key锁(图中红框中的lock mode S)。
如果我们的INSERT语句还带有ON DUPLICATE KEY... 这样的子句,如果遇到主键值或者唯一二级索引列值重复的情况,会对B+树中已存在的相同键值的记录加X型锁,而不是S型锁(不过具体锁的具体类型是和前面描述一样的)。
好了,又到了看代码求证时间了,我们看一下吧:
row_ins_scan_sec_index_for_duplicate是检测唯一二级索引列值是否重复的函数,具体加锁的代码如下所示:
如上图所示,在遇到唯一二级索引列重复的情况时:
1号红框表示对带有ON DUPLICATE ...子句时的处理方案,具体就是添加X型锁。
2号红框表示对正常INSERT语句的处理方案,具体就是添加S型锁。
不过不论是那种情况,添加的lock_typed的值都是LOCK_ORDINARY,表示next-key锁。
在主键重复时INSERT语句的加锁代码我们就不列举了。
3. 外键检查时
当我们向子表中插入记录时,我们分两种情况讨论:
当子表中的外键值可以在父表中找到时,那么无论当前事务是什么隔离级别,只需要给父表中对应的记录添加一个S型正经记录锁就好了。
当子表中的外键值在父表中找不到时:那么如果当前隔离级别不大于RC时,不对父表记录加锁;当隔离级别不小于RR时,对父表中该外键值所在位置的下一条记录添加gap锁。
死锁要出场了
好了,基础知识预习完了,该死锁出场了。
看下边这个平平无奇的INSERT语句:
INSERT INTO hero(name, country) VALUES('g关羽', '蜀'), ('d邓艾', '魏');
这个语句用来插入两条记录,不论是在RC,还是RR隔离级别,如果两个事务并发执行它们是有一定几率触发死锁的。为了稳定复现这个死锁,我们把上边一条语句拆分成两条语句:
INSERT INTO hero(name, country) VALUES('g关羽', '蜀'); INSERT INTO hero(name, country) VALUES('d邓艾', '魏');
拆分前和拆分后起到的作用是相同的,只不过拆分后我们可以人为的控制插入记录的时机。如果T1和T2的执行顺序是这样的:
也就是:
T1先插入name值为g关羽的记录,可以插入成功,此时对应的唯一二级索引记录被隐式锁保护,我们执行SHOW ENGINE INNODB STATUS语句,发现啥一个行锁(row lock)都没有(因为SHOW ENGINE INNODB STATUS不显示隐式锁):
接着T2也插入name值为g关羽的记录。由于T1已经插入name值为g关羽的记录,所以T2在插入二级索引记录时会遇到重复的唯一二级索引列值,此时T2想获取一个S型next-key锁,但是T1并未提交,T1插入的name值为g关羽的记录上的隐式锁相当于一个X型正经记录锁(RC隔离级别),所以T2向获取S型next-key锁时会遇到锁冲突,T2进入阻塞状态,并且将T1的隐式锁转换为显式锁(就是帮助T1生成一个正经记录锁的锁结构)。这时我们再执行SHOW ENGINE INNODB STATUS语句:
可见,T1持有的name值为g关羽的隐式锁已经被转换为显式锁(X型正经记录锁,lock_mode X locks rec but not gap);T2正在等待获取一个S型next-key锁(lock mode S waiting)。
接着T1再插入一条name值为d邓艾的记录。在插入一条记录时,会在页面中先定位到这条记录的位置。在插入name值为d邓艾的二级索引记录时,发现现在页面中的记录分布情况如下所示:
很显然,name值为'd邓艾'的二级索引记录所在位置的下一条二级索引记录的name值应该是'g关羽'(按照汉语拼音排序)。那么在T1插入name值为d邓艾的二级索引记录时,就需要看一下name值为'g关羽'的二级索引记录上有没有被别的事务加gap锁。
有同学想说:目前只有T2想在name值为'g关羽'的二级索引记录上添加S型next-key锁(next-key锁包含gap锁),但是T2并没有获取到锁呀,目前正在等待状态。那么T1不是能顺利插入name值为'g关羽'的二级索引记录么?
我们看一下执行结果:
# 事务T2 mysql> INSERT INTO hero(name, country) VALUES('g关羽', '蜀'); ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
很显然,触发了一个死锁,T2被InnoDB回滚了。
这是为啥呢?T2明明没有获取到name值为'g关羽'的二级索引记录上的S型next-key锁,为啥T1还不能插入入name值为d邓艾的二级索引记录呢?
这我们还得回到代码上来,看一下插入新记录时是如何判断锁是否冲突的:
看一下画红框的注释,意思是:只要别的事务生成了一个显式的gap锁的锁结构,不论那个事务已经获取到了该锁(granted),还是正在等待获取(waiting),当前事务的INSERT操作都应该被阻塞。
回到我们的例子中来,就是T2已经在name值为'g关羽'的二级索引记录上生成了一个S型next-key锁的锁结构,虽然T2正在阻塞(尚未获取锁),但是T1仍然不能插入name值为d邓艾的二级索引记录。
这样也就解释了死锁产生的原因:
T1在等待T2释放name值为'g关羽'的二级索引记录上的gap锁。
T2在等待T1释放name值为'g关羽'的二级索引记录上的X型正经记录锁。
两个事务相互等待对方释放锁,这样死锁也就产生了。
怎么解决这个死锁问题?
两个方案:
方案一:一个事务中只插入一条记录。
方案二:先插入name值为'd邓艾'的记录,再插入name值为'g关羽'的记录
推荐学习:mysql视频教程
以上是一起聊聊兩條INSERT語句引發的死鎖的詳細內容。更多資訊請關注PHP中文網其他相關文章!

MySQL數據庫升級的步驟包括:1.備份數據庫,2.停止當前MySQL服務,3.安裝新版本MySQL,4.啟動新版本MySQL服務,5.恢復數據庫。升級過程需注意兼容性問題,並可使用高級工具如PerconaToolkit進行測試和優化。

MySQL備份策略包括邏輯備份、物理備份、增量備份、基於復制的備份和雲備份。 1.邏輯備份使用mysqldump導出數據庫結構和數據,適合小型數據庫和版本遷移。 2.物理備份通過複製數據文件,速度快且全面,但需數據庫一致性。 3.增量備份利用二進制日誌記錄變化,適用於大型數據庫。 4.基於復制的備份通過從服務器備份,減少對生產系統的影響。 5.雲備份如AmazonRDS提供自動化解決方案,但成本和控制需考慮。選擇策略時應考慮數據庫大小、停機容忍度、恢復時間和恢復點目標。

MySQLclusteringenhancesdatabaserobustnessandscalabilitybydistributingdataacrossmultiplenodes.ItusestheNDBenginefordatareplicationandfaulttolerance,ensuringhighavailability.Setupinvolvesconfiguringmanagement,data,andSQLnodes,withcarefulmonitoringandpe

在MySQL中優化數據庫模式設計可通過以下步驟提升性能:1.索引優化:在常用查詢列上創建索引,平衡查詢和插入更新的開銷。 2.表結構優化:通過規範化或反規範化減少數據冗餘,提高訪問效率。 3.數據類型選擇:使用合適的數據類型,如INT替代VARCHAR,減少存儲空間。 4.分區和分錶:對於大數據量,使用分區和分錶分散數據,提升查詢和維護效率。

tooptimizemysqlperformance,lofterTheSeSteps:1)inasemproperIndexingTospeedUpqueries,2)使用ExplaintplaintoAnalyzeandoptimizequeryPerformance,3)ActiveServerConfigurationStersLikeTlikeTlikeTlikeIkeLikeIkeIkeLikeIkeLikeIkeLikeIkeLikeNodb_buffer_pool_sizizeandmax_connections,4)

MySQL函數可用於數據處理和計算。 1.基本用法包括字符串處理、日期計算和數學運算。 2.高級用法涉及結合多個函數實現複雜操作。 3.性能優化需避免在WHERE子句中使用函數,並使用GROUPBY和臨時表。

MySQL批量插入数据的高效方法包括:1.使用INSERTINTO...VALUES语法,2.利用LOADDATAINFILE命令,3.使用事务处理,4.调整批量大小,5.禁用索引,6.使用INSERTIGNORE或INSERT...ONDUPLICATEKEYUPDATE,这些方法能显著提升数据库操作效率。

在MySQL中,添加字段使用ALTERTABLEtable_nameADDCOLUMNnew_columnVARCHAR(255)AFTERexisting_column,刪除字段使用ALTERTABLEtable_nameDROPCOLUMNcolumn_to_drop。添加字段時,需指定位置以優化查詢性能和數據結構;刪除字段前需確認操作不可逆;使用在線DDL、備份數據、測試環境和低負載時間段修改表結構是性能優化和最佳實踐。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

Atom編輯器mac版下載
最受歡迎的的開源編輯器

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。