資料庫鎖機制詳解:樂觀鎖、悲觀鎖與死鎖的愛恨情仇

用生活化的比喻理解資料庫中的並發控制機制

為什麼需要鎖?

想像一個沒有鎖的世界:你正在 ATM 提款,同時你的另一半也在另一台 ATM 提款。帳戶只有 1000 元,你們都想提 800 元。如果沒有鎖的保護,可能兩個人都成功提款,銀行就虧大了!

鎖就是資料庫用來防止這種混亂的機制。

😊 樂觀鎖(Optimistic Lock)

什麼是樂觀鎖?

樂觀鎖就像是一個樂觀的人,總是假設不會有衝突發生。

生活中的比喻

圖書館借書的例子

樂觀的做法:

  1. 你看到一本好書,記下它的位置
  2. 去泡杯咖啡,逛逛其他區域
  3. 準備借書時才回來拿
  4. 如果書還在 → 成功借走
  5. 如果書不見了 → 接受現實,找別本

這就是樂觀鎖的精神:先假設沒人跟你搶,真的被搶了再說

樂觀鎖的實現方式

1. 版本號方式

就像文件的版本管理:

  • Word 文件顯示「版本 1.0」
  • 你開始編輯時記住是 1.0 版
  • 儲存時檢查:還是 1.0 版嗎?
    • 是 → 儲存成功,變成 1.1 版
    • 否 → 有人改過了,需要處理衝突

2. 時間戳方式

像是看商品的「最後更新時間」:

  • 商品顯示「最後更新:10:30」
  • 你 10:35 修改價格
  • 提交時檢查:最後更新還是 10:30 嗎?
    • 是 → 更新成功
    • 否 → 有人在你之前改過了

樂觀鎖的優缺點

優點

  • 不需要真的「鎖住」資源
  • 讀取時完全不阻塞
  • 適合讀多寫少的場景
  • 沒有死鎖風險

缺點

  • 衝突時需要重試
  • 高併發寫入時效率低
  • 可能造成「飢餓」(一直失敗)

適用場景

  • 電商商品瀏覽:大家都在看,偶爾才有人買
  • 部落格系統:讀者多,作者少
  • 配置管理:查看配置頻繁,修改較少

😰 悲觀鎖(Pessimistic Lock)

什麼是悲觀鎖?

悲觀鎖就像是一個謹慎的人,總是假設會有衝突發生。

生活中的比喻

公廁的例子

悲觀的做法:

  1. 進廁所第一件事:鎖門
  2. 裡面的人安心使用
  3. 外面的人只能等待
  4. 用完開門,下一位

這就是悲觀鎖的精神:先鎖起來,確保沒人能搶

悲觀鎖的類型

1. 共享鎖(讀鎖)

像是博物館參觀:

  • 多人可以同時參觀(讀取)
  • 但參觀時不能裝修(寫入)
  • 要裝修必須等所有人離開

2. 排他鎖(寫鎖)

像是試衣間:

  • 一次只能一個人使用
  • 其他人不能進入,連看都不行
  • 必須等裡面的人出來

悲觀鎖的實現

行級鎖 vs 表級鎖

  • 行級鎖:只鎖一個座位(影響小)
  • 表級鎖:鎖整個餐廳(影響大)

就像餐廳訂位:

  • 預訂一張桌子 → 行級鎖
  • 包場整個餐廳 → 表級鎖

悲觀鎖的優缺點

優點

  • 強一致性保證
  • 適合寫操作頻繁的場景
  • 實現簡單直觀

缺點

  • 可能造成阻塞
  • 降低並發性能
  • 有死鎖風險

適用場景

  • 銀行轉帳:絕對不能出錯
  • 庫存扣減:防止超賣
  • 搶票系統:一個座位只能賣給一個人

☠️ 死鎖(Deadlock)

什麼是死鎖?

死鎖是指兩個或多個操作互相等待對方釋放資源,導致都無法繼續的情況。

經典比喻

哲學家就餐問題

五個哲學家圍坐圓桌:

  • 每人面前一盤意大利麵
  • 每兩人之間一支叉子(共5支)
  • 吃麵需要兩支叉子

如果每個人都先拿起左手邊的叉子,然後等右手邊的叉子:

  • 每人手上都有一支叉子
  • 每人都在等別人放下叉子
  • 所有人都餓死了!

死鎖的四個必要條件

1. 互斥條件

  • 叉子不能同時被兩人使用
  • 資源獨占

2. 持有並等待

  • 拿著一支叉子,等另一支
  • 不釋放已有資源

3. 不可剝奪

  • 不能搶別人手上的叉子
  • 資源只能自願釋放

4. 循環等待

  • A等B,B等C,C等A
  • 形成等待環路

現實中的死鎖

十字路口的例子

四輛車同時到達十字路口:

  • 每輛車都佔據一個方向
  • 每輛車都等右邊的車先走
  • 結果誰都動不了

程式中的死鎖範例

小明的流程:
1. 鎖住 A 資源
2. 需要 B 資源(但被小華鎖住了)
3. 等待...

小華的流程:
1. 鎖住 B 資源
2. 需要 A 資源(但被小明鎖住了)
3. 等待...

結果:永遠等待!

死鎖的預防

1. 破壞互斥條件

  • 使用讀寫鎖代替排他鎖
  • 但有些資源天生就是互斥的

2. 破壞持有並等待

  • 一次性申請所有資源
  • 要麼全拿,要麼都不拿

3. 破壞不可剝奪

  • 等待超時自動放棄
  • 優先級高的可以搶奪資源

4. 破壞循環等待

  • 規定資源申請順序
  • 例如:永遠先申請 A 再申請 B

死鎖的檢測與恢復

銀行家算法: 像銀行放貸一樣謹慎:

  • 評估每個請求是否安全
  • 只有確保不會死鎖才批准
  • 寧可保守也不冒險

死鎖檢測

  • 定期檢查是否有循環等待
  • 發現死鎖立即處理

死鎖恢復

  1. 終止進程:踢掉一個哲學家
  2. 資源剝奪:搶走某人的叉子
  3. 回滾:讓某人放下叉子重來

🤔 如何選擇?

選擇指南

樂觀鎖適合

  • 讀多寫少
  • 衝突概率低
  • 可以接受重試

悲觀鎖適合

  • 寫多讀少
  • 衝突概率高
  • 數據強一致性

實戰建議

1. 從樂觀鎖開始

  • 實現簡單
  • 性能較好
  • 出問題再換悲觀鎖

2. 關鍵業務用悲觀鎖

  • 金錢相關
  • 庫存相關
  • 不能出錯的地方

3. 永遠防範死鎖

  • 設置超時時間
  • 保持加鎖順序一致
  • 盡快釋放鎖

🎯 實際案例

電商秒殺

問題:1000人搶10件商品

樂觀鎖方案

  • 每人讀取庫存和版本號
  • 提交訂單時檢查版本號
  • 失敗的人重試
  • 適合商品較多的情況

悲觀鎖方案

  • 要買先鎖定商品
  • 其他人等待
  • 確保不會超賣
  • 適合商品極少的情況

轉帳系統

場景:A轉帳給B,B轉帳給A

預防死鎖

  • 規定:永遠先鎖帳號小的
  • A < B,所以都先鎖 A
  • 避免循環等待

🏁 總結

鎖機制是數據庫並發控制的核心:

樂觀鎖

  • 像樂觀的人,相信世界是美好的
  • 適合衝突少的場景
  • 發現問題再處理

悲觀鎖

  • 像謹慎的人,防患於未然
  • 適合衝突多的場景
  • 提前預防問題

死鎖

  • 像交通堵塞,互相等待
  • 需要預防和處理機制
  • 是悲觀鎖的副作用

記住:沒有最好的鎖,只有最適合的鎖。 根據實際場景選擇,才能在性能和正確性之間找到平衡。


🔗 延伸閱讀

下期預告

《分散式鎖詳解:從 Redis 到 ZooKeeper》- 探討跨節點的鎖實現。

0%