成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

服了!DELETE 同一行記錄也會造成死鎖!!

數據庫 MySQL
本文將基于 MySQL 5.7.21 版本進行討論,該版本使用 InnoDB 存儲引擎,并采用 Repeated Read 作為事務隔離級別。

1.問題背景

“哥們,又雙叒叕寫了個死鎖,秀啊!??”

圖片圖片

就算是經常寫死鎖的同學看到估計都會有點懵,兩條一模一樣的 DELETE 語句怎么會產生死鎖呢?

2.MySQL 鎖回顧

看到這里的靚仔肯定對 MySQL 的鎖非常了解,哥們還是帶大家對鎖的分類進行快速回顧;

本文將基于 MySQL 5.7.21 版本進行討論,該版本使用 InnoDB 存儲引擎,并采用 Repeated Read 作為事務隔離級別。

圖片圖片

要查看 MySQL 的加鎖信息,必須啟用 InnoDB 狀態監控功能;

SET GLOBAL innodb_status_output=ON;
SET GLOBAL innodb_status_output_locks=ON;

要獲取 InnoDB 存儲引擎的詳細狀態信息,可以使用以下 SQL 命令;

SHOW ENGINE INNODB STATUS;

3.DELETE 流程

在深入分析問題原因之前先對 DELETE 操作的基本流程進行復習。眾所周知,MySQL 以頁作為數據的基本存儲單位,每個頁內包含兩個主要的鏈表:正常記錄鏈表和垃圾鏈表。每條記錄都有一個記錄頭,記錄頭中包括一個關鍵屬性——deleted_flag。

圖片圖片

執行 DELETE 操作期間,系統首先將正常記錄的記錄頭中的 delete_flag 標記設置為 1。這一步驟也被稱為 delete mark,是數據刪除流程的一部分。

圖片圖片

在事務成功提交之后,由 purge 線程 負責對已標記為刪除的數據執行邏輯刪除操作。這一過程包括將記錄從正常記錄鏈表中移除,并將它們添加到垃圾鏈表中,以便后續的清理工作。

圖片圖片

針對不同狀態下的記錄,MySQL 在加鎖時采取不同的策略,特別是在處理唯一索引上記錄的加鎖情況。以下是具體的加鎖規則:

  • 正常記錄: 對于未被標記為刪除的記錄,MySQL 會施加記錄鎖,以確保事務的隔離性和數據的一致性。
  • delete mark: 當記錄已被標記為刪除(即 delete_flag 被設置為1),但尚未由 purge 線程清理時,MySQL 會對這些記錄施加臨鍵鎖,以避免在清理前發生數據沖突。
  • 已刪除記錄: 對于已經被 purge 線程邏輯刪除的記錄,MySQL 會施加間隙鎖,這允許在已刪除記錄的索引位置插入新記錄,同時保持索引的完整性和順序性。

4.原因剖析

在分析死鎖的案例中,我們關注的表 t_order_extra_item_15 具有一個由 (order_id, extra_key) 組成的聯合唯一索引。為了更好地理解死鎖的產生機制,我們將對上述死鎖日志進行簡化處理。


事務137060372(A)

事務137060371(B)

執行語句

delete from t_order_extra_item_15 WHERE (order_id = xxx and extra_key = xxx)

delete from t_order_extra_item_15 WHERE (order_id = xxx and extra_key = xxx)

持有鎖


lock_mode X locks rec but not gap(記錄鎖)

等待鎖

lock_mode X locks rec but not gap waiting(記錄鎖)

lock_mode X waiting(臨鍵鎖)

事務 A 試圖獲取記錄鎖,但被事務 B 持有的相同的記錄鎖所阻塞。而且,事務 B 在嘗試獲取臨鍵鎖時也遇到了阻塞,這是因為事務 A 先前已經請求了記錄鎖,從而形成了一種相互等待的狀態,這種情況最終導致了死鎖的發生。

然而事務 B 為何在已經持有記錄鎖的情況下還需要等待臨鍵鎖?唯一合理的解釋是,在事務 B 最初執行 DELETE 操作時,它所嘗試操作的記錄已經被其他事務鎖定。當這個其他事務完成了 delete mark 并提交后,事務 B 不得不重新發起對臨鍵鎖的請求。

經過深入分析得出結論,在并發環境中,必然存在另一個執行相同 DELETE 操作的事務,我們稱之為事務 C。

通過仔細分析業務代碼和服務日志,我們迅速驗證了這一假設。現在,導致死鎖的具體原因已經非常明顯。為了幫助大家更好地理解三個事務的執行順序,我們制定了一個事務執行時序的設想表格。

事務 A

事務 B

事務 C



1. delete from t_order_extra_item_15 WHERE (order_id = xxx and extra_key = xxx ) )

獲取記錄鎖成功(lock_mode X locks rec but not gap)


2. delete from t_order_extra_item_15 WHERE (order_id = xxx and extra_key = xxx ) )

等待獲取記錄鎖( lock_mode X locks rec but not gap waiting)


3. delete from t_order_extra_item_15 WHERE (order_id = xxx and extra_key = xxx ) )

等待獲取記錄鎖( lock_mode X locks rec but not gap waiting)





4. delete mark 設置記錄頭刪除標識位

delete_flag=1



5. 事務提交


6. 獲取記錄鎖成功

記錄狀態變更重新獲取臨鍵鎖(lock_mode X)


7. 發現死鎖,回滾該事務

WE ROLL BACK TRANSACTION




8. 事務提交


在執行流程的第 6 步中,事務 B 嘗試重新獲取臨鍵鎖,這時與事務 A 發生了相互等待的狀況,導致死鎖的發生。為解決這一問題,數據庫管理系統自動回滾了事務 A,以打破死鎖狀態。

5.現場還原

哥們深知道理論分析至關重要,實踐才是檢驗真理的唯一標準。Talk is cheap, Show me the code. 在相同的系統環境下,我們創建了一個測試表來模擬實際情況;

CREATE TABLE `t_lock` (
  `id` int NOT NULL,
  `uniq` int NOT NULL,
  `idx` int NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq` (`uniq`) USING BTREE,
  KEY `idx` (`idx`)
);

INSERT INTO t_lock VALUES (1, 1, 1);
INSERT INTO t_lock VALUES (5, 5, 5);
INSERT INTO t_lock VALUES (10, 10, 10);

大聰明一上來便直接手動開啟 3 個 MySQL 命令列界面,每個界面中獨立開啟事務執行 DELETE FROM t_lock where uniq = 5; 語句,然而實驗結果并未能成功復現先前討論的死鎖狀況。

經過反復 SHOW ENGINE INNODB STATUS; 檢查鎖的狀態得出結論:在 DELETE 操作中,加鎖和 delete mark 是連續的不可分割的步驟,不受人為干預。一旦一個事務開始執行 DELETE,其他事務對該記錄的訪問請求將自動轉為臨鍵鎖,避免了死鎖的發生。

為了更準確地模擬并發環境下 DELETE 操作可能導致的死鎖,這里采用 Java 語言編寫了一個示例程序;

public class Main {

    private static final String URL = "jdbc:mysql://localhost:3306/db_test";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";
    private static final String SQL = "DELETE FROM t_lock WHERE uniq = 5;";

    public static void main(String[] args) {
        // 開啟 3 個線程,模擬并發刪除
        for (int i = 0; i < 3; i++) {
            new Thread(Main::executeSQL).start();
        }
    }

    public static void executeSQL() {
        try (
                Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
                Statement statement = connection.createStatement()
        ) {
            System.out.println(LocalTime.now() + ":" + Thread.currentThread().getName());
            // 關閉自動提交
            connection.setAutoCommit(false);
            int rows = statement.executeUpdate(SQL);
            // 延時 5 秒便于觀察加鎖信息
            Thread.sleep(5000);
            connection.commit();
            System.out.println(LocalTime.now() + ":" + Thread.currentThread().getName() + ":" + rows);
        } catch (Exception e) {
            // 死鎖堆棧輸出
            e.printStackTrace();
        }
    }
}

果不其然,程序執行異常,異常堆棧中清晰地記錄了死鎖信息。進一步檢查 MySQL 服務端的死鎖日志,與線上業務的死鎖日志如出一轍。程序執行過程中三個并發事務的加鎖信息,和文章第四段的原因分析完全一致。這證實了我們的現場模擬成功復現了死鎖情況。

6 問題思考

6.1 可以通過 SELECT FOR UPDATE 避免嗎

不行。SELECT FOR UPDATE 的加鎖邏輯與 DELETE 語句的加鎖邏輯是一致的。加鎖的類型完全取決于被加鎖記錄的狀態。由于這一機制,使用 SELECT FOR UPDATE 并不能解決由 DELETE 操作引起的死鎖問題。

6.2 只有唯一索引會有這個問題嗎

的確,只有唯一索引會引發此類死鎖問題,主鍵索引和普通索引均不會。在上述的系統環境下的實驗結果表明,不同索引類型在索引等值加 X 鎖情況下的行為如下:


主鍵索引

唯一索引

普通索引

正常記錄

記錄鎖

記錄鎖

臨鍵鎖

delete mark

記錄鎖

臨鍵鎖

臨鍵鎖

已刪除記錄

間隙鎖

間隙鎖

間隙鎖

唯一索引在處理"正常記錄"時施加的是記錄鎖,但在處理處于"delete mark"狀態的記錄時,它施加的是臨鍵鎖。這種加鎖類型的不一致性,在執行并發的 DELETE 操作時,增加了導致死鎖的風險。

6.3 持有記錄鎖后再請求臨鍵鎖為什么需要等待

因為在同一行記錄上過去已經有事務在等待獲取鎖了,為了避免鎖饑餓現象的發生,先前請求加鎖的事務在鎖釋放后將獲得優先權。口說無憑,大聰明直接開啟 2 個 MySQL 命令列界面,分別執行 DELETE FROM t_lock where uniq = 5; 語句,實際操作結果如下;

事務 A

事務 B

1. delete from t_lock WHERE uniq = 5;

獲取記錄鎖成功(lock_mode X locks rec but not gap)


2. delete mark 設置記錄頭刪除標識位

delete_flag=1



3. delete from t_lock WHERE uniq = 5;

等待獲取臨鍵鎖( lock_mode X waiting)

4. delete from t_lock WHERE uniq = 5;

獲取臨鍵鎖成功(lock_mode X)



5. 發現死鎖,回滾該事務

WE ROLL BACK TRANSACTION

6. 事務提交


在操作流程的第四步中,事務 A 嘗試請求對 uniq = 5 的臨鍵鎖,發現事務 B 已經先行一步請求了同一行記錄上的臨鍵鎖。然而,事務 B 的這一請求由于事務 A 持有的記錄鎖而被阻塞,從而相互等待造成了死鎖現象。

6.4 高版本的 MySQL 會存在 DELETE 死鎖嗎

在 MySQL 環境 8.x 版本環境中,DELETE 操作引發的死鎖情況得到了改進。通過觀察加鎖日志發現,事務在對于 delete mark 的記錄加鎖時,如果已經持有了該記錄的記錄鎖,他將獲取間隙鎖而不是臨鍵鎖,這一變化有效避免了死鎖的發生。

具體的加鎖信息在此略去,大伙們若感興趣可以親自進行驗證。??

7 事后總結

問題的來龍去脈都已梳理清晰,解決方案可歸納為以下幾種:

  1. 升級 MySQL 版本: ?? 升級到最新版本可能會帶來人力成本和系統風險;
  2. 更改隔離級別 RC: ?? 可以解決死鎖問題,但會引入臟讀和幻讀現象;
  3. 放任不管: ?? 不影響數據一致性,會導致服務和數據庫出現異常;
  4. 引入分布式鎖: ?? 開發成本相對較小,且影響范圍可控,已被采納;

平日朗誦八股文時如濤濤江水連綿不絕,可實際業務場景總會遇到各種奇葩的問題。因此,我們應該始終對技術保持一顆敬畏之心,追求不斷學習和成長。

作者:曹建濤,轉轉C2C&寄賣業務研發工程師

參考資料

[1]InnoDB Locking: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html

[2]An InnoDB Deadlock Example: https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlock-example.html

責任編輯:武曉燕 來源: 轉轉技術
相關推薦

2024-08-01 09:57:17

DELETE死鎖工具

2020-04-01 08:51:47

注釋字符代碼

2020-08-24 08:25:48

Python開發工具

2021-07-26 10:32:54

MySQL數據庫存儲

2020-07-15 09:40:37

代碼Python瀏覽記錄

2022-11-30 17:13:05

MySQLDynamic存儲

2016-12-02 08:53:18

Python一行代碼

2020-08-05 11:53:41

數據代碼自動化

2014-02-12 13:43:50

代碼并行任務

2022-04-09 09:11:33

Python

2017-04-05 11:10:23

Javascript代碼前端

2024-06-12 08:21:07

Deadlock死鎖版本

2025-04-01 01:25:00

MySQLInnoDBMyISAM

2020-02-28 15:27:31

代碼開發工具

2021-10-19 17:52:16

Git命令算數

2021-08-31 09:49:37

CPU執行語言

2017-04-13 19:20:18

Python代碼并行任務

2018-01-02 09:26:28

Ubuntuzzupdate版本升級

2020-08-19 10:30:25

代碼Python多線程

2020-09-09 16:00:22

Linux進程
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品视频一二三区 | 免费一看一级毛片 | а天堂中文最新一区二区三区 | 超碰在线国产 | 欧美一级视频免费看 | 日日欧美 | 国产精品视频导航 | 国产1区2区3区 | 人人看人人草 | 色视频网站免费 | 中文字幕1区 | 亚洲一区视频 | 国产日韩一区二区三区 | 欧美日韩视频在线第一区 | av在线免费观看网站 | 成人av高清在线观看 | 久久爱一区 | 狠狠久 | 欧美精品中文字幕久久二区 | 97日日碰人人模人人澡分享吧 | 99久久久久 | 久草热播| 欧美在线观看一区 | 国产一区二区三区在线 | 精品区 | 黄页网址在线观看 | 免费在线观看av片 | 欧美另类视频在线 | 亚洲在线观看视频 | 99re热精品视频 | 亚洲 中文 欧美 | 亚洲不卡在线观看 | 国产成人在线视频 | 成人小视频在线观看 | 久久岛国| 久草网在线视频 | 国产精品一区二区不卡 | 久久天天综合 | 欧美不卡一区二区三区 | 免费在线观看一区二区三区 | 老司机精品福利视频 |