MySQL產(chǎn)生死鎖原因分析講解
鎖類型介紹
MySQL 有三種鎖的級(jí)別:頁(yè)級(jí)、表級(jí)、行級(jí)
1 表級(jí)鎖:開(kāi)銷小,加鎖快;不會(huì)出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突的概率最高, 并發(fā)度最低。
2 行級(jí)鎖:開(kāi)銷大,加鎖慢;會(huì)出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低, 并發(fā)度也最高。
3 頁(yè)面鎖:開(kāi)銷和加鎖時(shí)間界于表鎖和行鎖之間;會(huì)出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般。
死鎖產(chǎn)生原因和示例
1、產(chǎn)生原因
所謂死鎖 <DeadLock>
:是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過(guò)程中, 因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象, 若無(wú)外力作用,它們都將無(wú)法推進(jìn)下去. 此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。表級(jí)鎖不會(huì)產(chǎn)生死鎖. 所以解決死鎖主要還是針對(duì)于最常用的 InnoDB。
死鎖的關(guān)鍵在于:兩個(gè) (或以上) 的 Session 加鎖的順序不一致。
那么對(duì)應(yīng)的解決死鎖問(wèn)題的關(guān)鍵就是:讓不同的 session 加鎖有次序。
2、產(chǎn)生示例
案例一
需求:將投資的錢拆成幾份隨機(jī)分配給借款人。
起初業(yè)務(wù)程序思路是這樣的:
投資人投資后,將金額隨機(jī)分為幾份,然后隨機(jī)從借款人表里面選幾個(gè),然后通過(guò)一條條 select for update
去更新借款人表里面的余額等。
例如兩個(gè)用戶同時(shí)投資:
A 用戶金額隨機(jī)分為 2 份,分給借款人 1,2
B 用戶金額隨機(jī)分為 2 份,分給借款人 2,1
由于加鎖的順序不一樣,死鎖當(dāng)然很快就出現(xiàn)了。
對(duì)于這個(gè)問(wèn)題的改進(jìn)很簡(jiǎn)單,直接把所有分配到的借款人直接一次鎖住就行了。
Select * from xxx where id in (xx,xx,xx) for update
在 in 里面的列表值 mysql 是會(huì)自動(dòng)從小到大排序,加鎖也是一條條從小到大加的鎖例如(以下會(huì)話 id 為主鍵):
Session1:
mysql> select * from t3 where id in (8,9) for update; +----+--------+------+---------------------+ | id | course | name | ctime | +----+--------+------+---------------------+ | 8 | WA | f | 2016-03-02 11:36:30 | | 9 | JX | f | 2016-03-01 11:36:30 | +----+--------+------+---------------------+ rows in set (0.04 sec)
Session2:
select * from t3 where id in (10,8,5) for update;
鎖等待中……
其實(shí)這個(gè)時(shí)候 id=10 這條記錄沒(méi)有被鎖住的,但 id=5 的記錄已經(jīng)被鎖住了,鎖的等待在 id=8 的這里 不信請(qǐng)看。
Session3:
mysql> select * from t3 where id=5 for update;
鎖等待中
Session4:
mysql> select * from t3 where id=10 for update; +----+--------+------+---------------------+ | id | course | name | ctime | +----+--------+------+---------------------+ | 10 | JB | g | 2016-03-10 11:45:05 | +----+--------+------+---------------------+ row in set (0.00 sec)
在其它 session 中 id=5 是加不了鎖的,但是 id=10 是可以加上鎖的。
案例二
在開(kāi)發(fā)中,經(jīng)常會(huì)做這類的判斷需求:根據(jù)字段值查詢(有索引),如果不存在,則插入;否則更新。
以 id 為主鍵為例,目前還沒(méi)有 id=22 的行
Session1:
select * from t3 where id=22 for update; Empty set (0.00 sec)
session2:
select * from t3 where id=23 for update; Empty set (0.00 sec)
Session1:
insert into t3 values(22,'ac','a',now());
鎖等待中……
Session2:
insert into t3 values(23,'bc','b',now());
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
當(dāng)對(duì)存在的行進(jìn)行鎖的時(shí)候 (主鍵),mysql 就只有行鎖。
當(dāng)對(duì)未存在的行進(jìn)行鎖的時(shí)候 (即使條件為主鍵),mysql 是會(huì)鎖住一段范圍(有 gap 鎖)
鎖住的范圍為:
(無(wú)窮小或小于表中鎖住 id 的最大值,無(wú)窮大或大于表中鎖住 id 的最小值)
如:如果表中目前有已有的 id 為(11 , 12),那么就鎖?。?2,無(wú)窮大)
如果表中目前已有的 id 為(11 , 30),那么就鎖?。?1,30)
對(duì)于這種死鎖的解決辦法是:
insert into t3(xx,xx) on duplicate key update `xx`='XX';
用 mysql 特有的語(yǔ)法來(lái)解決此問(wèn)題。
因?yàn)?insert 語(yǔ)句對(duì)于主鍵來(lái)說(shuō),插入的行不管有沒(méi)有存在,都會(huì)只有行鎖。
案例三
mysql> select * from t3 where id=9 for update; +----+--------+------+---------------------+ | id | course | name | ctime | +----+--------+------+---------------------+ | 9 | JX | f | 2016-03-01 11:36:30 | +----+--------+------+---------------------+ row in set (0.00 sec)
Session2:
mysql> select * from t3 where id<20 for update;
鎖等待中…
Session1:
mysql> insert into t3 values(7,'ae','a',now());
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
這個(gè)跟案例一其它是差不多的情況,只是 session1 不按常理出牌了。
Session2 在等待 Session1 的 id=9 的鎖,session2 又持了 1 到 8 的鎖(注意 9 到 19 的范圍并沒(méi)有被 session2 鎖住),最后,session1 在插入新行時(shí)又得等待 session2, 故死鎖發(fā)生了。
這種一般是在業(yè)務(wù)需求中基本不會(huì)出現(xiàn),因?yàn)槟沔i住了 id=9,卻又想插入 id=7 的行,這就有點(diǎn)跳了,當(dāng)然肯定也有解決的方法,那就是重理業(yè)務(wù)需求,避免這樣的寫法。
案例四
一般的情況,兩個(gè) session 分別通過(guò)一個(gè) sql 持有一把鎖,然后互相訪問(wèn)對(duì)方加鎖的數(shù)據(jù)產(chǎn)生死鎖。
案例五
兩個(gè)單條的 sql 語(yǔ)句涉及到的加鎖數(shù)據(jù)相同,但是加鎖順序不同,導(dǎo)致了死鎖。
案例六
CREATE TABLE dltask ( id bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', a varchar(30) NOT NULL COMMENT 'uniq.a', b varchar(30) NOT NULL COMMENT 'uniq.b', c varchar(30) NOT NULL COMMENT 'uniq.c', x varchar(30) NOT NULL COMMENT 'data', PRIMARY KEY (id), UNIQUE KEY uniq_a_b_c (a, b, c) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='deadlock test';
a,b,c 三列,組合成一個(gè)唯一索引,主鍵索引為 id 列。
事務(wù)隔離級(jí)別:RR (Repeatable Read)
每個(gè)事務(wù)只有一條 SQL:
delete from dltask where a=? and b=? and c=?;
到此這篇關(guān)于MySQL產(chǎn)生死鎖原因分析講解的文章就介紹到這了,更多相關(guān)MySQL死鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL自動(dòng)填充create_time和update_time的兩種方式
當(dāng)我們創(chuàng)建業(yè)務(wù)表的時(shí)候 通常都需要設(shè)置create_time 和 update_time,下面這篇文章主要給大家介紹了關(guān)于MySQL自動(dòng)填充createTime和updateTime的兩種方式,需要的朋友可以參考下2022-05-05MySQL數(shù)據(jù)表合并去重的簡(jiǎn)單實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于MySQL數(shù)據(jù)表合并去重的簡(jiǎn)單實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用MySQL具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05MySQL查詢數(shù)據(jù)庫(kù)所有表名以及表結(jié)構(gòu)其注釋(小白專用)
查詢數(shù)據(jù)庫(kù)所有表的表名、備注,其實(shí)也是比較常見(jiàn)的操作,這篇文章主要給大家介紹了關(guān)于MySQL查詢數(shù)據(jù)庫(kù)所有表名以及表結(jié)構(gòu)其注釋的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2024-08-08MYSQL 性能分析器 EXPLAIN 用法實(shí)例分析
這篇文章主要介紹了MYSQL 性能分析器 EXPLAIN 用法,結(jié)合實(shí)例形式分析了MYSQL 性能分析器 EXPLAIN 基本功能、使用方法及操作注意事項(xiàng),需要的朋友可以參考下2020-05-05mysql根據(jù)json字段內(nèi)容作為查詢條件(包括json數(shù)組)檢索數(shù)據(jù)
本文主要介紹了mysql根據(jù)json字段內(nèi)容作為查詢條件(包括json數(shù)組)檢索數(shù)據(jù),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02記一次mysql5.7測(cè)試數(shù)據(jù)庫(kù)被刪表的問(wèn)題
這篇文章主要介紹了記一次mysql5.7測(cè)試數(shù)據(jù)庫(kù)被刪表的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11