1. 程式人生 > >MySQL鎖系列 之 死鎖

MySQL鎖系列 之 死鎖

一、什麼是死鎖

  • 1.必須滿足的條件
1. 必須有兩個或者兩個以上的事務
2. 不同事務之間都持有對方需要的鎖資源。 A事務需要B的資源,B事務需要A的資源,這就是典型的AB-BA死鎖
  • 2.死鎖相關的引數
* innodb_print_all_deadlocks

1. 如果這個引數開啟,那麼死鎖相關的資訊都會列印輸出到error log

* innodb_lock_wait_timeout

1. 當MySQL獲取row lock的時候,如果wait了innodb_lock_wait_timeout=N的時間,會報以下錯誤

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

* innodb_deadlock_detect

1. innodb_deadlock_detect = off  可以關閉掉死鎖檢測,那麼就發生死鎖的時候,用鎖超時來處理。
2. innodb_deadlock_detect = on  (預設選項)開啟死鎖檢測,資料庫自動回滾

* innodb_status_lock_output = on

1. 可以看到更加詳細的鎖資訊

二、死鎖有什麼危害

  1. 死鎖,即表明有多個事務之間需要互相爭奪資源而互相等待。
  2. 如果沒有死鎖檢測,那麼就會互相卡死,一直hang死
  3. 如果有死鎖檢測機制,那麼資料庫會自動根據代價來評估出哪些事務可以被回滾掉,用來打破這個僵局
  4. 所以說:死鎖並沒有啥壞處,官網:www.fhadmin.org  反而可以保護資料庫和應用
  5. 那麼出現死鎖,而且非常頻繁,我們應該調整業務邏輯,讓其避免產生死鎖方為上策

三、典型的死鎖案例剖析

3.1 死鎖案例一

典型的 官網:www.fhadmin.org AB-BA 死鎖

session 1:
    select * from tb_b where id_2 = 1 for
update (A) session 2: select * from tb_a where id = 2 for update (B) session 1: select * from tb_a where id = 2 for update (B) session 2: select * from tb_b where id_2 = 1 for update (A) ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction 1213的死鎖錯誤,mysql會自動回滾 哪個回滾代價最小,回滾哪個(根據undo判斷) ------------------------ LATEST DETECTED DEADLOCK ------------------------ 2017
-06-22 16:39:50 0x7f547dd02700 *** (1) TRANSACTION: TRANSACTION 133601982, ACTIVE 48 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 11900, OS thread handle 140000866637568, query id 25108 localhost dba statistics select * from tb_a where id = 2 for update -----session1 持有tb_a中記錄為2的鎖 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 303 page no 3 n bits 72 index PRIMARY of table `lc_5`.`tb_a` trx id 133601982 lock_mode X locks rec but not gap waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 80000002; asc ;; --session 1 需要tb_a中記錄為2的鎖( session1 -> session2 ) 1: len 6; hex 000007f69ab2; asc ;; 2: len 7; hex dc000027100110; asc ' ;; *** (2) TRANSACTION: TRANSACTION 133601983, ACTIVE 28 sec starting index read, thread declared inside InnoDB 5000 mysql tables in use 1, locked 1 4 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 11901, OS thread handle 140000864773888, query id 25109 localhost dba statistics select * from tb_b where id_2 = 1 for update *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 303 page no 3 n bits 72 index PRIMARY of table `lc_5`.`tb_a` trx id 133601983 lock_mode X locks rec but not gap Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 80000002; asc ;; --session 2 持有tb_a中記錄等於2的鎖 1: len 6; hex 000007f69ab2; asc ;; 2: len 7; hex dc000027100110; asc ' ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 304 page no 3 n bits 72 index PRIMARY of table `lc_5`.`tb_b` trx id 133601983 lock_mode X locks rec but not gap waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 80000001; asc ;; --session 2 需要tb_b中記錄為1的鎖 ( session2 -> session1 ) 1: len 6; hex 000007f69ab8; asc ;; 2: len 7; hex e0000027120110; asc ' ;; 最終的結果: 死鎖路徑:[session1 -> session2 , session2 -> session1] ABBA死鎖產生

3.2 死鎖案例二

同一個事務中,官網:www.fhadmin.org S-lock 升級為 X-lock 不能直接繼承

* session 1:

mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)

mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)

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

mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;   --獲取S-lock
+------+
| i    |
+------+
|    1 |
+------+

* session 2:

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

mysql> DELETE FROM t WHERE i = 1;   --想要獲取X-lock,但是被session1的S-lock 卡住,目前處於waiting lock階段



* session 1:

mysql> DELETE FROM t WHERE i = 1;   --想要獲取X-lock,session1本身擁有S-lock,但是由於session 2 獲取X-lock再前,所以session1不能夠從S-lock 提升到 X-lock,需要等待session2 釋放才可以獲取,所以造成死鎖
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction


死鎖路徑:
    session2 -> session1 , session1 -> session2


3.3 死鎖案例三

唯一鍵死鎖 (delete + insert)
關鍵點在於:S-lock

dba:lc_3> show create table uk;
+-------+--------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                 |
+-------+--------------------------------------------------------------------------------------------------------------+
| uk    | CREATE TABLE `uk` (
  `a` int(11) NOT NULL,
  UNIQUE KEY `uniq_a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------+--------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


dba:lc_3> select * from uk;
+---+
| a |
+---+
| 1 |
+---+
1 row in set (0.00 sec)


session 1:

dba:lc_3> begin;
Query OK, 0 rows affected (0.00 sec)

dba:lc_3> delete from uk where a=1;
Query OK, 1 row affected (0.00 sec)

session 2:

dba:(none)> use lc_3;
Database changed
dba:lc_3> insert into uk values(1);  --wait lock(想要加S-lock,卻被sesson1的X-lock卡住)


sesson 3:

dba:(none)> use lc_3;
Database changed
dba:lc_3> insert into uk values(1); --wait lock(想要加S-lock,卻被sesson1的X-lock卡住)


session 1:

commit;    --session2和session3 都獲得了S-lock,然後都想要去給記錄1 加上X-lock,卻互相被對方的S-lock卡住,死鎖產生


再來看session 2 和 session 3 的結果:

session2:
Query OK, 1 row affected (7.36 sec)

session3:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction


總結: 試想想,如果session 1 不是commit,而是rollback會是怎麼樣呢? 大家去測測就會發現,結果肯定是唯一鍵衝突啊



3.4 死鎖案例四

主鍵和二級索引的死鎖

* primary key

1   2   3   4   --primary key col1

10  30  20  40  --idx_key2 col2

100 200 300 400  --idx_key3 col3


* idx_key2      select * from t where col2 > 10: 鎖二級索引順序為:20 =》30 , 對應鎖主鍵的順序為:3 =》2

10 20 30 40

1  3  2  4


* idx_key3    select * from t where col3 > 100:鎖二級索引順序為:200 =》300 , 對應鎖主鍵的順序為:2 =》3

100 200 300 400

1   2   3   4


死鎖路徑:
    由於二級索引引起的主鍵加鎖順序: 3 =》2
    由於二級索引引起的主鍵加鎖順序: 2 =》3

這個要求併發,且剛好

session 1 加鎖3的時候 session 2 要加鎖2.
session 1 加鎖2的時候 session 3 要加鎖3.

這樣就產生了 AB-BA 死鎖

3.5 死鎖案例五

purge + unique key 引發的死鎖

A表的記錄: id =  1    10   40   100    200   500  800  900

session 1 :
    delete from a where id = 10;   ???

session 2 :
    delete from a where id = 800;  ???

session 1 :
    insert into a select 800; ???

session 2 :
    insert into a select 10; ???

* 如果大家去跑這兩鍾SQL語句的併發測試,是可以導致死鎖的。

* 如何驗證是由於purge導致的問題呢?這個本想用mysqld-debug模式去關閉purge執行緒,但是很遺憾我沒能模擬出來。。。

3.6 死鎖案例六

REPLACE INTO問題

* 這個問題模擬起來非常簡單,原理非常複雜,這裡不過多解釋
    * 詳情請看姜老師的文章,據說看懂了年薪都100w了:  http://www.innomysql.com/26186-2/

* 解決方案:
    * 用insert into ... on duplicate key update 代替 replace into
    * 此方案親測有效

四、如何避免死鎖

  • 產生死鎖的原因
1. 事務之間互相佔用資源

  • 方法和總結
1. 降低隔離級別,修改 RR -> RC , 如果這個調整了,可以避免掉60%的死鎖場景和奇怪的鎖等待

2. 調整業務邏輯和SQL,讓其都按照順序執行操作

3. 減少unique索引,大部分死鎖的場景都是由於unique索引導致

4. 儘量不用replace into,用insert into ... on duplicate key update 代替