MySQL 你好,死鎖

原文地址:MySQL 你好,死鎖mysql

圖片描述

前言

在平常的生活中,相信你們曾或多或少有這麼一種體驗:"每到下班高峯期的時候,本來寬坦的交通幹道,一時間變得風雨不透,司機和乘客都煩躁不安,喇叭聲響成一片,當車卡在十字路口中間,會很尷尬的發現,此時不管想走哪都…..."。對於這樣的體驗,你們都是十分的懼怕接觸和體驗,交通部門也無時無刻爲解決交通擁堵問題而努力。git

其實上面生活案例中擁堵就相似於——高併發場景;github

而全部方向的車堵在十字路口中間就相似於——數據庫死鎖場景。算法

本章主要圍繞InnoDB存儲引擎死鎖相關的一些概念、產生死鎖的緣由、死鎖場景以及死鎖的處理策略。sql

相關概念

爲了更好的認識死鎖,咱們先來了解MySQL中與死鎖相關的一些基本概念。數據庫

併發控制

併發控制(Concurrency control)指的是當多個用戶同時更新運行時,用於保護數據庫完整性的各類技術。安全

讀寫鎖

爲了保證數據庫的併發控制,所以MySQL設置了兩種鎖:session

  • 共享鎖(Shared Lock):也叫讀鎖(Read Lock),容許多個鏈接能夠同一時刻併發的讀取同一資源,互不干擾
  • 排他鎖(Exclusive Lock):也叫寫鎖(Write Lock),會阻塞其餘寫鎖或者讀書的請求,保證同一時刻只有一個鏈接能夠操做數據,包括讀

鎖策略

所謂鎖策略就是在鎖的開銷和數據的安全性之間尋求平衡,這種平衡會影響到性能。目前InnoDB存儲引擎有如下兩種鎖策略:併發

  • Table Lock(表鎖)策略:最基本的鎖策略,開銷最小,加鎖快,不會出現死鎖,但發生鎖衝突機率高,粒度大,併發低
  • Row Lock(行鎖)策略:粒度最小,發生鎖衝突態度低,併發也高,可是開銷大,加鎖慢,會出現死鎖

事務

所謂事務,它是一個操做序列,這些操做要麼都執行,要麼都不執行,它是一個不可分割的工做單位。一個事務是須要經過嚴格ACID測試的:高併發

  • 原子性(ATOMICITY):一個事務的整個操做,要麼所有提交成功,要麼所有失敗回滾,不能執行其中的某一部分
  • 一致性(CONSISTENCY):數據庫老是從一個一致性的狀態轉換到另一個一致性的狀態
  • 隔離性(ISOLATION):一個事物所做的修改在提交前,其餘事務是看不到的
  • 持久性(DURABILITY):一旦事務提交,則其所作的修改就會永久保存到數據庫中

隔離級別

SQL標準制定了四種隔離級別,規定事務的修改對其它事務是否可見

  • READ UNCOMMITED(未提交讀):未提交也可見,又稱髒讀
  • READ COMMITED (提交讀):只有提交纔可見,大多數DBMS默認隔離級別都是這個,MySQL不是,也稱不可重複讀
  • REPEATABLE READ (可重複讀),屢次重複讀取結果一致,MySQL默認這個級別,解決髒讀問題,但存在幻讀問題(某個事務讀取記錄時,另外一事務插入了新紀錄,原事務再讀取記錄時產生幻行)。
  • SERIALIZABLE (可串行化),最高隔離級別,強制事務串行執行,避免了前面說的幻讀問題,併發性能差
隔離級別 髒讀可能性 不可重複讀可能性 幻讀可能性 加鎖讀
READ UNCOMMITED Yes Yes Yes No
READ COMMITED No Yes Yes No
REPEATABLE READ No No Yes No
SERIALIZABLE No No No Yes

死鎖的定義

死鎖是指兩個或多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源(我等待你的資源,你卻等待個人資源,咱們都相互等待,誰也不釋放本身佔有的資源),從而致使惡性循環的現象:

  • 當多個事務試圖以不一樣順序鎖定資源時,就可能會產生死鎖
  • 多個事務,同時鎖定同一個資源時,也會產生死鎖

死鎖的危害

死等和死鎖可不是一回事,若是你遇到了死等,大可放心,確定不是死鎖;若是發生了死鎖,也大可放心,絕對不會死等。

這是由於MySQL內部有一套死鎖檢測機制,一旦發生死鎖會當即回滾一個事務,讓另外一個事務執行下去。而且這個死鎖回滾的的錯誤消息也會發送給客戶端。即便正常的業務中,死鎖也時不時會發生,因此遇到死鎖不要懼怕,由於這也是對數據安全的一種保護,可是若死鎖太頻繁,那可能會帶來許多的問題:

  1. 使進程得不到正確的結果:處於死鎖狀態的進程得不到所需的資源,不能向前推動,故得不到結果
  2. 使資源的利用率下降:處於死鎖狀態的進程不釋放已佔有的資源,以致於這些資源不能被其餘進程利用,故系統資源利用率下降
  3. 致使產生新的死鎖:其它進程因請求不到死鎖進程已佔用的資源而沒法向前推動,因此也會發生死鎖

死鎖產生的緣由

死鎖有四個必要的條件:

  1. 互斥排他:一個資源每次只能被一個進程使用
  2. 保持着排他資源又提出新資源請求:一個進程因請求資源而阻塞時,對已得到的資源保持不放
  3. 不可剝奪:資源不能被搶佔,即資源只能在進程完成任務後自動釋放
  4. 環路:有一組等待進程{P0、P一、P2},P0等待的資源被P1所佔有,P1等待的資源被P2所佔有,而P2等待的又被P0所佔有,造成了一個等待循環

死鎖的發生場景

如下的全部場景是基於 InnoDB存儲引擎而且隔離級別爲REPEATABLE-READ(可重複讀)

查詢當前的隔離級別:

select @@global.tx_isolation,@@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+

修改隔離級別:

set global transaction isolation level read committed; ## 全局的

set session transaction isolation level read committed; ## 當前會話(session)

建立數據表

CREATE TABLE `deadlock` (
  `id` int(11) NOT NULL,
  `stu_num` int(11) DEFAULT NULL,
  `score` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_uniq_stu_num` (`stu_num`),
  KEY `idx_score` (`score`)
) ENGINE=InnoDB;

insert into deadlock(id, stu_num, score) values (1, 11, 111);
insert into deadlock(id, stu_num, score) values (2, 22, 222);
insert into deadlock(id, stu_num, score) values (3, 33, 333);

id主鍵索引

stu_num 爲惟一索引

score普通索引

爲了模擬實際場景,須要在每一個會話(session)中執行如下兩條命令:

set autocommit=0; ## 關閉自動提交

START TRANSACTION; ## 開始事務

場景一:AB BA

# session A
select * from deadlock where id = 1 for update; 

# session B
select * from deadlock where id = 2 for update; 

# session A
select * from deadlock where id = 2 for update;
## 由於session2 已經給id=2分配了寫鎖

# session B
select * from deadlock where id = 1 for update;
## 1213 - Deadlock found when trying to get lock; try restarting transaction

場景二:同一個事務中,S-lock 升級爲 X-lock

# session A
SELECT * FROM deadlock WHERE id = 1 LOCK IN SHARE MODE;   
## 獲取S-Lock

# session B
DELETE FROM deadlock WHERE id = 1;   
## 想獲取X-Lock,但被session A的S-lock 卡住,目前處於waiting lock階段

# session A
DELETE FROM deadlock WHERE id = 1;   
## Error : Deadlock found when trying to get lock; try restarting transaction
## 想獲取X-Lock,sessionA自己擁有S-Lock
## 可是因爲sessionB 申請X-Lock再前##
## 所以sessionA不可以從S-lock 提高到 X-lock
## 須要等待sessionB 釋放才能夠獲取,因此形成死鎖

場景三:主鍵和二級索引的死鎖

CREATE TABLE `deadlock_A` (
  `id` int(11) NOT NULL,
  `stu_num` int(11) DEFAULT NULL,
  `score` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_score` (`score`),
  KEY `idx_stu_num` (`stu_num`) USING BTREE
) ENGINE=InnoDB;

# deadlock_A 數據
# select * from deadlock_A
| id   | stu_num | score |
| ---- | ------- | ----- |
| 1    | 11      | 111   |
| 2    | 33      | 222   |
| 3    | 22      | 333   |
| 4    | 44      | 444   |
# session A
delete from deadlock_A where stu_num > 11;
## 鎖二級索引(stu_num)的順序:22->33->44  鎖主鍵(id)索引的順序:3->2->4

# session B
delete from deadlock_A where score > 111;
## 鎖二級索引(score)的順序:222->333->444  鎖主鍵(id)索引的順序:2->3->4

## sessionA鎖主鍵3, sessionB鎖主鍵2
## sessionA鎖主鍵2, sessionB鎖主鍵3
## 死鎖產生-》AB BA
## 這個在併發場景,可能會產生。

場景四:間隙鎖(Gap Lock)

CREATE TABLE `t2` (
  `id` int(11) NOT NULL,
  `v` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_v` (`v`) USING BTREE
) ENGINE=InnoDB;

# select * from t2
| id   | v       |
| ---- | ----- |
| 2    | 2     |
| 5    | 5     |
| 10   | 10    |

間隙鎖案例

# session A
delete from test where v=5;

# session B
insert into t2 (id,v) values (3,3);
## ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

insert into t2 (id,v) values (9,9);
## ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

insert into t2 (id,v) values (5,11);
## ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

insert into t2 (id,v) values (1,1)
## Affected rows : 1, Time: 5.62sec

insert into t2(id,v) values (10, 10);
## Affected rows : 1, Time: 10.51sec

insert into t2 (id,v) values (9,11);
## Affected rows : 1, Time: 15.51sec

看得出鎖的是 id=5 & k=[3,10)的記錄。

經過上面案例,大概瞭解間隙鎖的範圍後,咱們來看看死鎖場景:

# session A
update t2 set v = 5 where v =5;
## Affected rows : 1, Time: 12.67sec

# session B
update t2 set v = 10 where v =10;
## Affected rows : 1, Time: 12.88sec

# session A
insert into t2 (id,v) values (7,7);
## waiting

# session B
insert into t2 (id,v) values (8,8);
## Error : Deadlock found when trying to get lock; try restarting transaction

死鎖的處理策略

預防死鎖

  1. 同順序:以固定的順序訪問表和行。好比兩個更新數據的事務,事務A 更新數據的順序 爲1->2;事務B更新數據的順序爲2->1。這樣更可能會形成死鎖
  • 儘可能保持事務簡短:大事務更傾向於死鎖,若是業務容許,將大事務拆小
  • 一次性鎖定:在同一個事務中,儘量作到一次鎖定所須要的全部資源,減小死鎖機率
  • 下降隔離級別:若是業務容許,將隔離級別調低也是較好的選擇,好比將隔離級別從RR調整爲RC,能夠避免掉不少由於gap鎖形成的死鎖
  • 細粒度鎖定(行鎖):爲表添加合理的索引。能夠看到若是不走索引將會爲表的每一行記錄添加上鎖,死鎖的機率大大增大

死鎖的檢測和解除

innodb_lock_wait_timeout 等待鎖超時回滾事務:
直觀方法是在兩個事務相互等待時,當一個等待時間超過設置的某一閥值時,對其中一個事務進行回滾,另外一個事務就能繼續執行。這種方法簡單有效,在innodb中,參數innodb_lock_wait_timeout用來設置超時時間。

wait-for graph算法來主動進行死鎖檢測:
innodb還提供了wait-for graph算法來主動進行死鎖檢測,每當加鎖請求沒法當即知足須要並進入等待時,wait-for graph算法都會被觸發。

參考文章

《高性能的MySQL 第三版》

http://hedengcheng.com/?p=771...

https://www.kancloud.cn/hangh...

https://blog.csdn.net/dqjyong...

相關文章
相關標籤/搜索