深入理解死結(Deadlock)與發生條件

更新日期: 2025 年 3 月 4 日

在電腦科學與資料庫管理中,「死結(Deadlock)」 是一種常見的問題,通常發生在多個執行緒或交易(Transaction)同時競爭資源時。

當一組交易彼此無限期地等待對方釋放資源,而這些資源又不會主動釋放時,就會發生死結

舉個簡單的例子:

  • 交易 A 鎖住 資源 X,並且在等待 資源 Y
  • 交易 B 鎖住 資源 Y,但也在等待 資源 X
  • 兩者互相等待對方釋放資源,導致無限等待的情況,這就是死結

什麼是死結(Deadlock)?

死結的定義

死結(Deadlock)是指兩個或以上的程序(或交易)無限期地互相等待對方釋放資源,導致系統進入僵局,無法繼續執行。

死結的例子

範例 1️⃣:兩輛車在狹窄道路上互相阻擋

想像一下,兩輛車同時進入一條只能容納一輛車的狹窄巷道:

  1. 車 A 來自西邊,進入巷道後等待車 B 讓路。
  2. 車 B 來自東邊,進入巷道後等待車 A 讓路。
  3. 兩輛車都不願意倒車,也無法前進,導致交通堵塞,這就是死結。

範例 2️⃣:資料庫中的交易死結

考慮以下 SQL 交易:

-- 交易 A:先鎖住 accounts 表的 ID = 1,再試圖鎖住 ID = 2
BEGIN;
LOCK TABLE accounts IN EXCLUSIVE MODE WHERE id = 1;
LOCK TABLE accounts IN EXCLUSIVE MODE WHERE id = 2;

-- 交易 B:先鎖住 accounts 表的 ID = 2,再試圖鎖住 ID = 1
BEGIN;
LOCK TABLE accounts IN EXCLUSIVE MODE WHERE id = 2;
LOCK TABLE accounts IN EXCLUSIVE MODE WHERE id = 1;

此時:

  • 交易 A 持有 ID = 1,等待 ID = 2
  • 交易 B 持有 ID = 2,等待 ID = 1
  • 兩者互相等待,導致死結。
graph TD;
    A["交易 A"]
    B["交易 B"]
    X["資源 X (ID = 1)"]
    Y["資源 Y (ID = 2)"]

    A -->|鎖定| X
    B -->|鎖定| Y
    A -.->|等待| Y
    B -.->|等待| X

死結的必要條件(Necessary Conditions)

要發生死結,系統必須滿足以下四個條件(如果其中任何一個條件不成立,死結就不會發生)。

條件描述
互斥(Mutual Exclusion)資源一次只能被一個進程(或交易)持有,不能同時被多個使用者存取。
持有並等待(Hold and Wait)一個交易在持有某些資源的同時,還會繼續請求新的資源,而不釋放已持有的資源。
不可剝奪(No Preemption)資源不能被強制收回,只有持有該資源的交易可以自願釋放。
循環等待(Circular Wait)交易形成一個環狀的資源等待鏈,互相等待對方釋放資源。

互斥條件(Mutual Exclusion)

🔹 資源只能被一個交易使用,無法共享

這表示當某個交易或程序已經持有某個資源時,其他交易就不能同時使用這個資源,必須等它釋放後才能拿到。

📌 舉例

假設有 一張 ATM 機,當 A 用戶 正在操作時,B 用戶 不能同時使用這台機器,必須等 A 完成操作後才能輪到 B。

持有並等待(Hold and Wait)

🔹 交易在持有資源的同時,還在等待其他資源

這表示一個交易已經鎖定了一部分資源,但它還想要更多資源,而這些資源可能已經被其他交易鎖住。

📌 舉例

1️⃣ A(男生) 已經向身邊的朋友透露自己喜歡 B(女生)持有「對 B 的感情」這個資源),但他不想自己先開口,希望 B 先告白等待「B 的主動表白」這個資源)。

2️⃣ B(女生) 其實也喜歡 A,並且也向朋友表達過對 A 的好感(持有「對 A 的感情」這個資源),但她不想自己先開口,希望 A 先告白等待「A 的主動表白」這個資源)。

3️⃣ 結果

  • A 不願意先告白,因為他還在等 B 先開口
  • B 也不願意先告白,因為她還在等 A 先開口
  • 雙方都已經擁有對彼此的愛意(持有資源),但誰也不願意放棄「等對方先開口」的機會(等待資源),結果雙方陷入長期曖昧,關係停滯不前,形成感情上的死結。

不可剝奪(No Preemption)

🔹 資源不能被強制奪走,只有持有資源的交易可以釋放

這表示當一個交易持有某個資源時,系統無法強制把資源拿走,只能等交易自己釋放。

📌 舉例

  • A 用戶 取款時,ATM 機內部已經開始處理交易,但突然 B 用戶急著用 ATM
  • 系統不會強制中斷 A 用戶的交易,然後讓 B 用戶先用,因為資源只能被持有者自己釋放
  • B 只能無限等待,直到 A 用戶操作結束,ATM 機才會空出來。

循環等待(Circular Wait)

🔹 交易之間形成「等待環」,互相等待對方釋放資源,導致無限卡住

這表示一組交易在環狀的等待關係中,每個交易都在等待另一個交易釋放資源,但沒有人能先釋放,最終所有交易都無限等待,導致死結。

📌 舉例

  1. 交易 A 持有資源 X,並等待資源 Y
  2. 交易 B 持有資源 Y,並等待資源 X
  3. 交易 A 無法執行,因為 B 不釋放資源 Y;交易 B 也無法執行,因為 A 不釋放資源 X
  4. 這形成了一個環狀等待(Circular Wait),導致雙方都被卡住,進入死結。

死結的影響

  • 系統無法繼續執行:所有涉及的交易都陷入等待狀態,導致應用程式無法正常運作。
  • 效能下降:系統資源被鎖住,影響其他交易的執行,導致整體效能下降。
  • 數據一致性風險:如果死結發生且沒有被適當處理,可能會導致數據不一致,甚至影響數據完整性。

預防死結的方法(如何避免死結發生?)

死結通常是因為交易互相等待對方釋放資源而產生,因此可以透過以下方法來降低發生死結的機率。

資源請求順序統一

當交易請求多個資源時,應該統一請求的順序,確保所有交易按照相同的順序來請求資源。這樣可以避免「循環等待(Circular Wait)」條件的產生。

📌 示例: 假設我們的系統有兩種資源:

  • 資源 X(例如:客戶帳戶資料)
  • 資源 Y(例如:交易記錄表)

交易請求的順序應該統一,例如:

  • 交易 A 先請求 資源 X,再請求 資源 Y
  • 交易 B 也必須先請求 資源 X,再請求 資源 Y

🚫 錯誤情況(可能導致死結):

  • 交易 A 先鎖住 資源 X,然後等待 資源 Y
  • 交易 B 先鎖住 資源 Y,然後等待 資源 X
  • 兩者形成「循環等待」,導致死結

正確做法(可避免死結):

  • 所有交易都統一「先請求 X,再請求 Y」,這樣交易之間不會互相等待資源而卡住。

設置資源請求超時(Timeout)

當交易嘗試獲取資源時,應該設定最大等待時間,如果超過這個時間仍然無法獲取資源,則自動放棄並回滾交易,避免死結的發生。

📌 MySQL 設置交易鎖等待時間(10 秒)

SET innodb_lock_wait_timeout = 10;

這表示:

  • 若交易等待超過 10 秒仍無法獲取所需的資源,則自動回滾該交易,釋放已持有的資源,讓其他交易可以繼續執行。

這種方法可以確保系統不會因為某一筆交易長時間卡住資源,而影響其他交易的執行。

避免「持有並等待」(Hold and Wait)

當交易在請求新資源之前,應該先釋放已持有的資源,避免交易同時持有一部分資源,並且等待額外的資源,導致死結。

📌 正確做法(確保交易不會長時間持有資源)

BEGIN;
LOCK TABLE accounts IN EXCLUSIVE MODE WHERE id = 1;
COMMIT;  -- 先釋放已鎖住的資源
BEGIN;
LOCK TABLE accounts IN EXCLUSIVE MODE WHERE id = 2;
COMMIT;

這樣可以確保:

  • 交易在請求新資源前,已經釋放舊資源,減少交易長時間鎖住多個資源的機率。
  • 避免「持有部分資源 + 等待其他資源」的情況,從而減少死結的發生。

如何解除死結?

當死結已經發生時,系統應該主動偵測並解除,否則交易會無限期地互相等待,導致系統效能下降或無法執行新的交易。

檢測死結並終止交易

大部分的資料庫管理系統(如 MySQL、PostgreSQL)內建死結偵測機制,當偵測到死結時,會強制回滾其中一個交易,讓其他交易能夠繼續執行。

📌 查詢 MySQL 當前的死結資訊

SHOW ENGINE INNODB STATUS;

這條指令可以顯示:

  • 當前發生的死結交易
  • 哪些交易互相等待
  • 交易佔用了哪些資源

如何解決?

  • 讓系統自動回滾某些交易(通常是等待時間最長的交易)。
  • 手動終止某個交易,讓其他交易繼續執行

手動終止(KILL)特定交易

如果系統沒有自動解決死結,或者我們希望手動介入,則可以使用 KILL 指令來強制終止某個交易,讓其他交易能夠繼續執行。

📌 步驟 1:查詢當前正在執行的交易

SHOW PROCESSLIST;

這條指令會顯示目前系統內正在執行的所有交易,包含:

  • id(交易 ID)
  • user(執行交易的使用者)
  • host(來源主機)
  • db(影響的資料庫)
  • command(執行中的指令)
  • time(交易執行時間)
  • state(交易狀態,如「Waiting for table lock」)

📌 步驟 2:終止特定交易

KILL <transaction_id>;

假設我們發現交易 ID 為 1234 的交易導致死結,我們可以執行:

KILL 1234;

這樣就能強制終止該交易,釋放其持有的資源,讓其他交易可以繼續執行。


結論

死結(Deadlock)發生時,交易互相等待對方釋放資源,導致無限期卡住,影響系統效能。

死結發生的必要條件有四個:互斥、持有並等待、不可剝奪、循環等待。只要打破其中一個條件,就能預防死結。

常見的預防策略包括設定資源請求順序、設置超時機制、優化資源管理等。

如果死結已發生,可透過資料庫的死結偵測機制、超時回滾或手動終止交易來解決。

方法作用適用於
資源請求順序統一統一交易請求資源的順序,避免循環等待預防死結
設置超時機制設定最大等待時間,自動回滾超時的交易預防死結
避免持有並等待先釋放舊資源再請求新資源,減少交易長時間鎖住資源預防死結
檢測死結並終止交易系統自動偵測死結,並回滾其中一個交易解決死結
手動終止特定交易強制終止死結交易,釋放資源解決死結

掌握這些概念後,你就能有效預防與解決死結問題,確保資料庫系統的穩定運行! 💡

Similar Posts