欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Mysql如何解決死鎖問題

 更新時間:2025年04月23日 11:38:01   作者:fixAllenSun  
這篇文章主要介紹了Mysql如何解決死鎖問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

【一】Mysql中鎖分類和加鎖情況

【1】按鎖的粒度分類

全局鎖

加鎖情況:使用 FLUSH TABLES WITH READ LOCK 語句,它會對整個數(shù)據(jù)庫實例加鎖,使整個數(shù)據(jù)庫處于只讀狀態(tài)。常用于全量備份等場景,確保備份期間數(shù)據(jù)的一致性。

示例:

FLUSH TABLES WITH READ LOCK;
-- 進行備份操作
UNLOCK TABLES;

表級鎖

1、表共享讀鎖(Table Read Lock)

特點

  • 1、允許多個事務(wù)同時對同一個表加共享讀鎖,即可以有多個事務(wù)同時讀取該表的數(shù)據(jù)。
  • 2、持有共享讀鎖的事務(wù)只能對表進行讀操作,不能進行寫操作。并且在持有該鎖期間,不能訪問其他未被鎖定的表。
  • 3、其他事務(wù)也可以讀取該表,但如果要對該表進行寫操作,則需要等待所有共享讀鎖釋放。

加鎖情況

  • 使用 LOCK TABLES table_name READ 語句,其他事務(wù)可以讀取該表,但不能寫入,當前持有讀鎖的事務(wù)也不能寫入其他表。
  • 常用于多個事務(wù)同時讀取同一表,且不允許有寫操作的場景。

示例:

-- 會話 1
LOCK TABLES users READ;
SELECT * FROM users;
-- 若嘗試寫入,會報錯
-- UPDATE users SET name = 'new_name' WHERE id = 1; 
UNLOCK TABLES;

-- 會話 2
SELECT * FROM users; -- 可以正常讀取

2、表獨占寫鎖(Table Write Lock)

特點

  • 1、同一時間只有一個事務(wù)能對表加獨占寫鎖。
  • 2、持有該鎖的事務(wù)可以對表進行讀寫操作,在其釋放鎖之前,其他事務(wù)無法對該表進行任何讀寫操作。

加鎖情況

  • 使用 LOCK TABLES table_name WRITE 語句,持有該鎖的事務(wù)可以對表進行讀寫操作,其他事務(wù)不能對該表進行讀寫,直到鎖釋放。
  • 用于對表進行數(shù)據(jù)修改,需要保證數(shù)據(jù)一致性的場景。

示例:

-- 會話 1
LOCK TABLES users WRITE;
SELECT * FROM users;
UPDATE users SET name = 'new_name' WHERE id = 1;
UNLOCK TABLES;

-- 會話 2
-- 若在會話 1 持有寫鎖期間嘗試讀寫,會被阻塞
SELECT * FROM users; 

3、元數(shù)據(jù)鎖(MDL)

特點:

  • 1、分為共享元數(shù)據(jù)鎖(Shared MDL)和排他元數(shù)據(jù)鎖(Exclusive MDL)。當對表進行 SELECT、INSERT、UPDATE、DELETE 等操作時,會自動加共享 MDL 鎖;當對表結(jié)構(gòu)進行修改(如 ALTER TABLE)時,會加排他 MDL 鎖。
  • 2、共享 MDL 鎖之間可以共存,即多個事務(wù)可以同時對同一個表加共享 MDL 鎖進行讀寫操作。但排他 MDL 鎖與其他任何類型的 MDL 鎖都互斥,也就是說,當一個事務(wù)持有排他 MDL 鎖時,其他事務(wù)無法對該表進行任何操作,直到排他 MDL 鎖釋放。

加鎖情況:

  • 當對表進行 SELECT、INSERT、UPDATE、DELETE 等操作時,會自動加共享 MDL 鎖;當對表結(jié)構(gòu)進行修改(如 ALTER TABLE)時,會加排他 MDL 鎖。
  • 目的是保證在表結(jié)構(gòu)修改時,不會有其他事務(wù)對表進行讀寫操作,避免數(shù)據(jù)不一致。
-- 會話 1
START TRANSACTION;
SELECT * FROM users; -- 自動加共享 MDL 鎖
-- 此時會話 2 可以進行讀操作,但不能進行表結(jié)構(gòu)修改

-- 會話 2
-- 可以正常讀取
SELECT * FROM users; 
-- 若執(zhí)行 ALTER TABLE 會被阻塞
-- ALTER TABLE users ADD COLUMN new_column VARCHAR(255); 

-- 會話 1 提交事務(wù)釋放共享 MDL 鎖
COMMIT;

3、意向鎖(Intention Lock)

加鎖方式:

  • 是一種表級別的鎖,在使用行級鎖時會自動添加相應(yīng)的意向鎖。

特點:

  • 1、分為意向共享鎖(IS)和意向排他鎖(IX)。當事務(wù)要對表中的某一行加共享鎖時,會先對表加意向共享鎖;當事務(wù)要對表中的某一行加排他鎖時,會先對表加意向排他鎖。
  • 2、意向鎖的作用是表明某個事務(wù)正在對表中的行進行加鎖操作,這樣在對表加更高級別的鎖(如表級共享鎖或表級排他鎖)時,可以快速判斷表中是否有行被鎖定,從而避免全表掃描。

示例

-- 會話 1
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 自動對表加意向排他鎖
-- 會話 2 嘗試對表加表級共享讀鎖會被阻塞
-- LOCK TABLES users READ; 
COMMIT;

行級鎖

1、記錄鎖(Record Lock)

(1)定義

記錄鎖是對索引記錄的鎖定,也就是對表中某一行數(shù)據(jù)的索引項加鎖。需要注意的是,記錄鎖總是會鎖定索引記錄,如果表沒有設(shè)置索引,MySQL 會自動創(chuàng)建一個隱藏的聚簇索引來使用。

(2)加鎖情況

在可重復(fù)讀或串行化隔離級別下,使用 SELECT … FOR UPDATE 或 UPDATE、DELETE 等語句對滿足條件的行記錄加鎖。例如:

SELECT * FROM table_name WHERE id = 1 FOR UPDATE;

執(zhí)行 UPDATE、DELETE 語句時,也會對操作的行記錄加記錄鎖。示例如下:

UPDATE users SET name = 'John' WHERE id = 1;
DELETE FROM users WHERE id = 1;

2、間隙鎖(Gap Lock)

(1)定義

間隙鎖鎖定的是索引記錄之間的間隙,其目的在于防止其他事務(wù)在該間隙插入新記錄,從而避免幻讀問題。

(2)加鎖情況

在可重復(fù)讀隔離級別下,當使用范圍查詢(如 WHERE id BETWEEN 1 AND 10)時,為了防止幻讀,會對查詢范圍的間隙加鎖。例如:

SELECT * FROM table_name WHERE id BETWEEN 1 AND 10 FOR UPDATE;

此語句不僅會對 id 在 1 到 10 之間的行記錄加鎖,還會對這些記錄之間的間隙加鎖,防止其他事務(wù)插入新的 id 在這個范圍內(nèi)的記錄。

3、臨鍵鎖(Next-Key Lock)

(1)定義

臨鍵鎖是記錄鎖和間隙鎖的組合,它會鎖定索引記錄本身以及該記錄前面的間隙。

(2)加鎖情況

是記錄鎖和間隙鎖的組合,在可重復(fù)讀隔離級別下,對索引記錄和其前面的間隙加鎖。常用于范圍查詢和唯一性檢查,防止幻讀和插入異常。

SELECT * FROM users WHERE id > 10 FOR UPDATE;

這個語句會對 id 大于 10 的行記錄及其前面的間隙加臨鍵鎖。

【2】按鎖的模式分類

共享鎖(S 鎖)

加鎖情況:使用 SELECT … LOCK IN SHARE MODE 語句對讀取的行記錄加共享鎖,多個事務(wù)可以同時對同一行記錄加共享鎖,但不能同時加排他鎖。例如:

SELECT * FROM table_name WHERE id = 1 LOCK IN SHARE MODE;

排他鎖(X 鎖)

加鎖情況:使用 SELECT … FOR UPDATE、UPDATE、DELETE 等語句對操作的行記錄加排他鎖,一旦某事務(wù)對行記錄加了排他鎖,其他事務(wù)既不能讀取也不能修改該行記錄,直到排他鎖被釋放。

【二】加鎖方式的影響因素

(1)隔離級別(Innodb默認:可重復(fù)讀-REPEATABLE READ)

不同的隔離級別對鎖的使用和加鎖范圍有影響。

例如,可重復(fù)讀隔離級別會使用間隙鎖和臨鍵鎖來防止幻讀,而讀提交隔離級別則不會。

(2)查詢語句

查詢條件、索引使用情況等會影響加鎖的范圍和粒度。如果使用索引進行精確匹配,可能只對匹配的行記錄加鎖;如果是范圍查詢,可能會加間隙鎖或臨鍵鎖。

(3)事務(wù)操作

不同的事務(wù)操作(如 SELECT、INSERT、UPDATE、DELETE)會觸發(fā)不同類型的鎖。例如,INSERT 操作可能會對插入位置的間隙加鎖,UPDATE 和 DELETE 操作會對操作的行記錄加排他鎖。

【三】Mysql的死鎖情況

【1】事務(wù)交叉更新導(dǎo)致死鎖

情況描述

假設(shè)有兩個事務(wù) T1 和 T2,以及一個表 accounts 包含 id 和 balance 兩列。

-- 事務(wù) T1
START TRANSACTION;
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
UPDATE accounts SET balance = balance - 100 WHERE id = 2;
COMMIT;

-- 事務(wù) T2
START TRANSACTION;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;

死鎖原因

(1)T1 先對 id = 1 的行加排他鎖,然后嘗試對 id = 2 的行加排他鎖;

(2)T2 先對 id = 2 的行加排他鎖,然后嘗試對 id = 1 的行加排他鎖。

(3)此時,T1 等待 T2 釋放 id = 2 的鎖,而 T2 等待 T1 釋放 id = 1 的鎖,從而形成死鎖。

【2】索引使用不當導(dǎo)致死鎖

情況描述

有一個表 orders 包含 order_id 和 product_id 兩列,product_id 上有索引。

-- 事務(wù) T1
START TRANSACTION;
UPDATE orders SET status = 'paid' WHERE product_id = 1;
UPDATE orders SET status = 'shipped' WHERE product_id = 2;
COMMIT;

-- 事務(wù) T2
START TRANSACTION;
UPDATE orders SET status = 'paid' WHERE product_id = 2;
UPDATE orders SET status = 'shipped' WHERE product_id = 1;
COMMIT;

死鎖原因

由于 product_id 上有索引,更新操作會對索引記錄和間隙加鎖。T1 和 T2 按照不同的順序?qū)?product_id 進行更新,導(dǎo)致鎖的獲取順序不一致,從而可能形成死鎖。

【3】并發(fā)插入導(dǎo)致的死鎖

情況描述

在可重復(fù)讀隔離級別下,兩個事務(wù)同時向一張有唯一索引的表中插入數(shù)據(jù),且插入的數(shù)據(jù)在唯一索引列上有沖突。InnoDB 為了保證數(shù)據(jù)的一致性,會使用間隙鎖,這可能導(dǎo)致死鎖。

-- 事務(wù)T1
START TRANSACTION;
INSERT INTO unique_table (id, value) VALUES (1, 'value1');

-- 事務(wù)T2
START TRANSACTION;
INSERT INTO unique_table (id, value) VALUES (1, 'value2');

解決方案

可以考慮將隔離級別調(diào)整為讀提交,但需要注意這可能會導(dǎo)致幻讀問題。

或者在插入數(shù)據(jù)前,先進行唯一性檢查,避免插入沖突的數(shù)據(jù)。

【4】外鍵約束引發(fā)的死鎖

情況描述

有兩張表,主表 A 和從表 B,從表 B 有外鍵關(guān)聯(lián)到主表 A。當兩個事務(wù)分別對主表和從表進行插入和刪除操作時,由于外鍵約束的檢查,可能會導(dǎo)致死鎖。

示例代碼:

-- 事務(wù)T1
START TRANSACTION;
INSERT INTO tableA (id, name) VALUES (1, 'name1');
-- 假設(shè)這里有一些耗時的操作
DELETE FROM tableB WHERE id = 1;

-- 事務(wù)T2
START TRANSACTION;
INSERT INTO tableB (id, a_id, value) VALUES (1, 1, 'value1');
-- 假設(shè)這里有一些耗時的操作
DELETE FROM tableA WHERE id = 1;

解決方案

確保在進行涉及外鍵關(guān)系的操作時,按照主表和從表的正確順序進行操作,或者使用級聯(lián)操作來簡化事務(wù)中的操作,減少鎖的競爭。

【5】??刪除不存在的數(shù)據(jù)導(dǎo)致間隙鎖

情況描述

??先delete,再insert,導(dǎo)致死鎖

實例的日志記錄表,實例在重跑的時候,會先根據(jù)instanceId去delete該實例關(guān)聯(lián)的全部舊的記錄信息,然后再陸續(xù)插入新的記錄信息,instanceId有索引,出現(xiàn)鎖超時的情況。在刪除的時候根據(jù)實例id刪除,但是記錄可能不存在,如果刪除的記錄在數(shù)據(jù)庫中存在,那么產(chǎn)生的就是普通的行鎖;當刪除的這條記錄不存在,會在刪除記錄所在的區(qū)間加間隙鎖。

背景信息

MySQL版本:Percona MySQL Server 5.7.19

隔離級別:可重復(fù)讀(RR)

業(yè)務(wù)邏輯:并發(fā)下按某個索引字段先delete記錄,再insert記錄

begin;
delete from tb where order_id = xxx;
insert into tb(order_id) values(xxx);
commit;

mysql鎖基本概念

  • S:共享鎖(行級鎖)
  • X:排他鎖(行級鎖)
  • IS:意向共享鎖(表級鎖),使用行級鎖時會自動添加相應(yīng)的意向鎖
  • IX:意向排他鎖(表級鎖),使用行級鎖時會自動添加相應(yīng)的意向鎖

鎖模式兼容性表

  • gap鎖與gap鎖之間不沖突
  • rec insert intention(插入意向鎖)與gap鎖沖突。

死鎖原因

打開參數(shù),從innodb status獲取更多的鎖信息。

set GLOBAL innodb_status_output_locks=ON;

表結(jié)構(gòu):

 CREATE TABLE `tb` (
  `order_id` int(11) DEFAULT NULL,
  KEY `idx_order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

表中數(shù)據(jù):

mysql> select * from tb;
+----------+
| order_id |
+----------+
|       10 |
|       20 |
+----------+
2 rows in set (0.00 sec)

事務(wù)執(zhí)行步驟:

(1)開啟兩個事務(wù)

(2)兩個事務(wù)分別刪除兩個個不存在的記錄

(3)兩個事務(wù)分別插入該記錄

當session1執(zhí)行delete from tb where order_id=15;,由于條件order_id=15的記錄不存在,session1 獲得2個鎖結(jié)構(gòu),分別是意向排他鎖IX(表級鎖)、gap鎖(行級鎖),如下:

---TRANSACTION 1055191443, ACTIVE 20 sec
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 315642, OS thread handle 139960342456064, query id 150462030 localhost root
TABLE LOCK table `db`.`tb` trx id 1055191443 lock mode IX
RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191443 lock_mode X locks gap before rec

當session2執(zhí)行delete from tb where order_id=15;,同樣由于order_id=15的記錄不存在,session2 也獲得2個鎖結(jié)構(gòu),分別是意向排他鎖IX(表級鎖)、gap鎖(行級鎖),如下:

---TRANSACTION 1055191444, ACTIVE 3 sec
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 315336, OS thread handle 139960562685696, query id 150462412 localhost root
TABLE LOCK table `db`.`tb` trx id 1055191444 lock mode IX
RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191444 lock_mode X locks gap before rec

當session2執(zhí)行insert into tb select 15;, session2 已經(jīng)獲取到IX鎖,gap鎖,等待 rec insert intention(插入意向鎖)

---TRANSACTION 1055191444, ACTIVE 68 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 315336, OS thread handle 139960562685696, query id 150462778 localhost root executing
insert into tb select 15
------- TRX HAS BEEN WAITING 2 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191444 lock_mode X locks gap before rec insert intention waiting
------------------
TABLE LOCK table `db`.`tb` trx id 1055191444 lock mode IX
RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191444 lock_mode X locks gap before rec
RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191444 lock_mode X locks gap before rec insert intention waiting

當session1執(zhí)行insert into tb select 15;,session1 已獲取到IX鎖,gap鎖, 等待rec insert intention(插入意向鎖), session1, session2 都在等待插入意向鎖, 插入意向鎖與gap鎖沖突,雙方都沒有釋放gap鎖,又都在等待插入意向鎖,死鎖發(fā)生。

LATEST DETECTED DEADLOCK
------------------------
2018-11-03 17:15:11 0x7f4b0e7ea700
*** (1) TRANSACTION:
TRANSACTION 1055191444, ACTIVE 135 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 315336, OS thread handle 139960562685696, query id 150462778 localhost root executing
insert into tb select 15
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191444 lock_mode X locks gap before rec insert intention waiting
*** (2) TRANSACTION:
TRANSACTION 1055191443, ACTIVE 201 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 315642, OS thread handle 139960342456064, query id 150463172 localhost root executing
insert into tb select 15
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191443 lock_mode X locks gap before rec
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191443 lock_mode X locks gap before rec insert intention waiting
*** WE ROLL BACK TRANSACTION (2)

案例擴展

以上死鎖案例,業(yè)務(wù)代碼邏輯是多線程并發(fā)下,有可能多個線程會執(zhí)行相同order_id的job,比如兩個線程執(zhí)行的order_id 都是15。

另外一種情況,多個線程間,不會執(zhí)行到相同order_id的情況,也可能發(fā)生死鎖。比如一個線程order_id=15,另外一個線程order_id=16,如下所示:

鎖情況與上述相同,不再贅述,死鎖信息如下:

LATEST DETECTED DEADLOCK
------------------------
2018-11-03 17:28:30 0x7f4b0e667700
*** (1) TRANSACTION:
TRANSACTION 1055191450, ACTIVE 18 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 316221, OS thread handle 139960338228992, query id 150467652 localhost root executing
insert into tb select 16
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191450 lock_mode X locks gap before rec insert intention waiting
*** (2) TRANSACTION:
TRANSACTION 1055191449, ACTIVE 28 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 316222, OS thread handle 139960340870912, query id 150467681 localhost root executing
insert into tb select 15
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191449 lock_mode X locks gap before rec
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1337 page no 4 n bits 72 index idx_order_id of table `db`.`tb` trx id 1055191449 lock_mode X locks gap before rec insert intention waiting
*** WE ROLL BACK TRANSACTION (2)
 

解決方案

1-修改隔離級別為提交讀(RC)

2-修改業(yè)務(wù)代碼邏輯,刪除記錄之前,先select,確認該記錄存在,再執(zhí)行delete刪除該記錄。

【6】同一個事務(wù)中多條update修改同一條記錄

情況描述

數(shù)據(jù)庫是Mysql 5.7,引擎是InnoDB,事務(wù)隔離級別是讀提交(READ-COMMITED)。

死鎖日志

Transactions deadlock detected, dumping detailed information.2019-03-19T21:44:23.516263+08:00 5877341 [Note] InnoDB: 
*** (1) TRANSACTION:TRANSACTION 173268495, ACTIVE 0 sec fetching rowsmysql tables in use 1, locked 1LOCK WAIT 304 lock struct(s), heap size 41168, 6 row lock(s), undo log entries 1MySQL thread id 5877358, OS thread handle 47356539049728, query id 557970181 11.183.244.150 fin_instant_app updating
update `fund_transfer_stream` set `gmt_modified` = NOW(), `state` = 'PROCESSING' where ((`state` = 'NEW') AND (`seller_id` = '38921111') AND (`fund_transfer_order_no` = '99010015000805619031958363857'))2019-03-19T21:44:23.516321+08:00 5877341 [Note] InnoDB: 
*** (1) HOLDS THE LOCK(S):RECORD LOCKS space id 173 page no 13726 n bits 248 index idx_seller_transNo of table `xxx`.`fund_transfer_stream` trx id 173268495 lock_mode X locks rec but not gapRecord lock, heap no 168 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
2019-03-19T21:44:23.516565+08:00 5877341 [Note] InnoDB: 
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 173 page no 12416 n bits 128 index PRIMARY of table `xxx`.`fund_transfer_stream` trx id 173268495 lock_mode X locks rec but not gap waitingRecord lock, heap no 56 PHYSICAL RECORD: n_fields 17; compact format; info bits 02019-03-19T21:44:23.517793+08:00 5877341 [Note] InnoDB: 
*** (2) TRANSACTION:TRANSACTION 173268500, ACTIVE 0 sec fetching rows, thread declared inside InnoDB 81mysql tables in use 1, locked 1302 lock struct(s), heap size 41168, 2 row lock(s), undo log entries 1MySQL thread id 5877341, OS thread handle 47362313119488, query id 557970189 11.131.81.107 fin_instant_app updating
update `fund_transfer_stream_0056` set `gmt_modified` = NOW(), `state` = 'PROCESSING' where ((`state` = 'NEW') AND (`seller_id` = '38921111') AND (`fund_transfer_order_no` = '99010015000805619031957477256'))2019-03-19T21:44:23.517855+08:00 5877341 [Note] InnoDB: 
*** (2) HOLDS THE LOCK(S):RECORD LOCKS space id 173 page no 12416 n bits 128 index PRIMARY of table `fin_instant_0003`.`fund_transfer_stream_0056` trx id 173268500 lock_mode X locks rec but not gapRecord lock, heap no 56 PHYSICAL RECORD: n_fields 17; compact format; info bits 0
2019-03-19T21:44:23.519053+08:00 5877341 [Note] InnoDB: 
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 173 page no 13726 n bits 248 index idx_seller_transNo of table `fin_instant_0003`.`fund_transfer_stream_0056` trx id 173268500 lock_mode X locks rec but not gap waitingRecord lock, heap no 168 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
2019-03-19T21:44:23.519297+08:00 5877341 [Note] InnoDB: *** WE ROLL BACK TRANSACTION (2)

定位導(dǎo)致死鎖的兩條sql

update `fund_transfer_stream_0056` set `gmt_modified` = NOW(), `state` = 'PROCESSING' where ((`state` = 'NEW') AND (`seller_id` = '38921111') AND (`fund_transfer_order_no` = '99010015000805619031957477256'))

update `fund_transfer_stream_0056` set `gmt_modified` = NOW(), `state` = 'PROCESSING' where ((`state` = 'NEW') AND (`seller_id` = '38921111') AND (`fund_transfer_order_no` = '99010015000805619031958363857'))

索引情況如下

KEY `idx_seller` (`seller_id`),
KEY `idx_seller_transNo` (`seller_id`,`fund_transfer_order_no`(20))

(1)事務(wù)1,持有索引idx_seller_transNo的鎖,在等待獲取PRIMARY的鎖。

(2)事務(wù)2,持有PRIMARY的鎖,在等待獲取idx_seller_transNo的鎖。

(3)因事務(wù)1和事務(wù)2之間發(fā)生循環(huán)等待,故發(fā)生死鎖。

事務(wù)1和事務(wù)2當前持有的鎖均為: lock_mode X locks rec but not gap ,兩個事務(wù)對記錄加的都是X 鎖,No Gap鎖,即對當行記錄加鎖,并未加間隙鎖。

死鎖原因

首先,此次死鎖一定是和Gap鎖以及Next-Key Lock沒有關(guān)系的。因為我們的數(shù)據(jù)庫隔離級別是讀提交(READ-COMMITED)的,這種隔離級別是不會添加Gap鎖的,gap鎖只有在讀未提交會用。前面的死鎖日志也提到這一點。

翻看代碼

@Transactional(rollbackFor = Exception.class)public int doProcessing(String sellerId, Long id, String fundTransferOrderNo) {    
    fundTreansferStreamDAO.updateFundStreamId(sellerId, id, fundTransferOrderNo);
    return fundTreansferStreamDAO.updateStatus(sellerId, fundTransferOrderNo, FundTransferStreamState.PROCESSING.name());
}

該代碼的目的是先后修改同一條記錄的兩個不同字段,updateFundStreamId SQL:

update fund_transfer_stream        set gmt_modified=now(),fund_transfer_order_no = #{fundTransferOrderNo}        where id = #{id} and seller_id = #{sellerId}
update fund_transfer_stream    set gmt_modified=now(),state = #{state}    where fund_transfer_order_no = #{fundTransferOrderNo} and seller_id = #{sellerId}    and state = 'NEW'

可以看到,我們的同一個事務(wù)中執(zhí)行了兩條Update語句,這里分別查看下兩條SQL的執(zhí)行計劃:

updateFundStreamId執(zhí)行的時候使用到的是PRIMARY索引。

updateStatus執(zhí)行的時候使用到的是idx_seller_transNo索引。

主要問題出在我們的idx_seller_transNo索引上面

索引創(chuàng)建語句中,我們使用了前綴索引,為了節(jié)約索引空間,提高索引效率,我們只選擇了fund_transfer_order_no字段的前20位作為索引值。

因為fund_transfer_order_no只是普通索引,而非唯一性索引。又因為在一種特殊情況下,會有同一個用戶的兩個fund_transfer_order_no的前20位相同,這就導(dǎo)致兩條不同的記錄的索引值一樣(因為seller_id 和fund_transfer_order_no(20)都相同 )。

就如本文中的例子,發(fā)生死鎖的兩條記錄的fund_transfer_order_no字段的值:99010015000805619031958363857和99010015000805619031957477256這兩個就是前20位相同的。

原因匯總

在MySQL中,行級鎖并不是直接鎖記錄,而是鎖索引。索引分為主鍵索引和非主鍵索引兩種,如果一條sql語句操作了主鍵索引,MySQL就會鎖定這條主鍵索引;如果一條語句操作了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關(guān)的主鍵索引。

(1)事務(wù)1執(zhí)行update1占用PRIMARY = 1的鎖

(2)事務(wù)2執(zhí)行update1 占有PRIMARY = 2的鎖;

(3)事務(wù)1執(zhí)行update2占有idx_seller_transNo = (3111095611,99010015000805619031)的鎖,嘗試占有PRIMARY = 2鎖失?。ㄗ枞?;

(4)事務(wù)2執(zhí)行update2嘗試占有idx_seller_transNo = (3111095611,99010015000805619031)的鎖失?。ㄋ梨i);

解決方案

(1)修改索引:只要我們把前綴索引 idx_seller_transNo中fund_transfer_order_no的前綴長度修改下就可以了。比如改成50。即可避免死鎖。

(2)解決辦法就是改代碼

所有update都通過主鍵ID進行。

在同一個事務(wù)中,避免出現(xiàn)多條update語句修改同一條記錄。

【四】排查線上死鎖問題

查看死鎖日志

MySQL 會將死鎖信息記錄在錯誤日志中,可以通過查看錯誤日志找到死鎖的詳細信息,包括死鎖發(fā)生的時間、涉及的事務(wù)和 SQL 語句等。

使用 SHOW ENGINE INNODB STATUS 命令

該命令可以顯示 InnoDB 存儲引擎的狀態(tài)信息,其中包含最近一次死鎖的詳細信息,如死鎖的事務(wù) ID、持有和等待的鎖等。

SHOW ENGINE INNODB STATUS;

開啟 innodb_print_all_deadlocks 參數(shù)

將該參數(shù)設(shè)置為 ON,可以讓 MySQL 記錄所有的死鎖信息到錯誤日志中,方便后續(xù)分析。

SET GLOBAL innodb_print_all_deadlocks = ON;

【五】解決死鎖問題

1、優(yōu)化事務(wù)邏輯

確保事務(wù)按照相同的順序訪問資源,避免交叉更新。例如,將上述事務(wù) T1 和 T2 都按照 id 從小到大的順序進行更新:

-- 事務(wù) T1
START TRANSACTION;
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
UPDATE accounts SET balance = balance - 100 WHERE id = 2;
COMMIT;


-- 事務(wù) T2
START TRANSACTION;
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
UPDATE accounts SET balance = balance - 100 WHERE id = 2;
COMMIT;

2、減少事務(wù)持有鎖的時間

盡量縮短事務(wù)的執(zhí)行時間,減少鎖的持有時間,降低死鎖的概率。例如,將大事務(wù)拆分成多個小事務(wù)。

3、調(diào)整隔離級別

如果業(yè)務(wù)允許,可以將隔離級別從可重復(fù)讀調(diào)整為讀提交,減少間隙鎖和臨鍵鎖的使用,降低死鎖的可能性。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

4、優(yōu)化索引

確保 SQL 語句使用合適的索引,避免全表掃描和范圍掃描,減少鎖的范圍和粒度。例如,為經(jīng)常用于查詢和更新的列添加索引。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 在Centos7中利用Shell腳本實現(xiàn)MySQL數(shù)據(jù)備份

    在Centos7中利用Shell腳本實現(xiàn)MySQL數(shù)據(jù)備份

    備份是容災(zāi)的基礎(chǔ),是指為防止系統(tǒng)出現(xiàn)操作失誤或系統(tǒng)故障導(dǎo)致數(shù)據(jù)丟失,而將全部或部分數(shù)據(jù)集合從應(yīng)用主機的硬盤或陣列復(fù)制到其它的存儲介質(zhì)的過程,本文將給大家介紹了在Centos7中利用Shell腳本實現(xiàn)MySQL數(shù)據(jù)備份,文中有詳細的圖文介紹,需要的朋友可以參考下
    2023-12-12
  • MySQL OOM 系列一 Linux內(nèi)存分配

    MySQL OOM 系列一 Linux內(nèi)存分配

    今天想提到的是線上一個4G的RDS實例,發(fā)生了OOM(out of memory)的問題,MySQL進程被直接Kill掉了。在解釋這個問題的時候,我們首先需要從Linux系統(tǒng)內(nèi)存分配策略講起
    2016-07-07
  • sql format()函數(shù)的用法及簡單實例

    sql format()函數(shù)的用法及簡單實例

    下面小編就為大家?guī)硪黄猻ql format函數(shù)()的用法及簡單實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-05-05
  • MySQL?臨時表的原理以及優(yōu)化方法

    MySQL?臨時表的原理以及優(yōu)化方法

    這篇文章主要介紹了MySQL?臨時表的原理以及優(yōu)化方法,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • Mysql性能調(diào)優(yōu)之max_allowed_packet使用及說明

    Mysql性能調(diào)優(yōu)之max_allowed_packet使用及說明

    這篇文章主要介紹了Mysql性能調(diào)優(yōu)之max_allowed_packet使用及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • MySQL 選擇合適的存儲引擎

    MySQL 選擇合適的存儲引擎

    這篇文章主要介紹了MySQL如何選擇合適的存儲引擎,幫助大家更好的理解和使用MySQL數(shù)據(jù)庫,感興趣的朋友可以了解下
    2020-09-09
  • Mysql自帶profiling性能分析工具使用分享

    Mysql自帶profiling性能分析工具使用分享

    Mysql自帶profiling性能分析工具使用分享,需要的朋友可以參考下
    2012-11-11
  • Windows服務(wù)器下MySql數(shù)據(jù)庫單向主從備份詳細實現(xiàn)步驟分享

    Windows服務(wù)器下MySql數(shù)據(jù)庫單向主從備份詳細實現(xiàn)步驟分享

    將主服務(wù)器中的MySql數(shù)據(jù)庫同步到從服務(wù)器中,使得對主服務(wù)器的操作可以即時更新到從服務(wù)器,避免主服務(wù)器因環(huán)境或者網(wǎng)絡(luò)異常一時無法使用,達到備份效果,這篇文章整理的確實挺詳細的
    2012-05-05
  • 圖文詳解Mysql中如何查看Sql語句的執(zhí)行時間

    圖文詳解Mysql中如何查看Sql語句的執(zhí)行時間

    寫程序的人往往需要分析所寫的SQL語句是否已經(jīng)優(yōu)化過了,服務(wù)器的響應(yīng)時間有多快,所以下面這篇文章主要給大家介紹了關(guān)于Mysql中如何查看Sql語句的執(zhí)行時間的相關(guān)資料,需要的朋友可以參考下
    2021-12-12
  • Dbeaver連接MySQL數(shù)據(jù)庫及錯誤Connection?refusedconnect處理方法

    Dbeaver連接MySQL數(shù)據(jù)庫及錯誤Connection?refusedconnect處理方法

    這篇文章主要介紹了dbeaver連接MySQL數(shù)據(jù)庫及錯誤Connection?refusedconnect處理方法,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-08-08

最新評論