關於mysql for update 排他鎖應用的問題分析

一、問題描述

最近工作中發現了一個系統問題,涉及到關於 mysql for update 排他鎖,沒有阻塞其他事務的問題。

主要的業務爲多個內配箱子入庫回傳修改箱子、內配單狀態,內配單狀態爲配貨完成狀態,沒有被修改爲上架完成狀態。

一個內配單可能存在多個商品,一個內配單發貨可能會發不同的箱子,每個箱子可能包含不同的內配單的商品。即內配單與箱子的關係是多對多。

二、原因分析

數據庫的隔離級別:讀已提交

2.1 箱子入庫回傳流程

箱子入庫回傳流程

舉例分析

select
  c.id as id
from
  table_c c
where
  c.id in (xxx, xxx)
  and not exists (
    select
      1
    from
      table_s s,
      table_b b
    where
      s.chuku_id = c.id
      and s.box_id = b.id
      and b.work_statue != '4'
  ) for
update

2.2 FOR UPDATE 語句的用法

在 MySQL 中,SELECT ... FOR UPDATE 語句是一種鎖定讀取操作,它鎖定語句返回的所有行,以防止其他事務在這些行上執行 UPDATEDELETE 操作直到當前事務提交。

以下是 FOR UPDATE 鎖定的一些關鍵點: 鎖定範圍

  • FOR UPDATE 會對查詢結果中的所有行設置排他鎖(X鎖)。
  • 如果查詢中包含了 WHERE 子句,那麼只有滿足條件的行會被鎖定。
  • 如果查詢中包含了 join 操作,那麼涉及的所有表的相關行都會被鎖定。 鎖定行爲
  • 其他事務不能對這些行執行 UPDATEDELETE 操作,直到當前事務提交。
  • 其他事務仍然可以讀取這些行,因爲 SELECT ... FOR UPDATE 不會阻止共享鎖(S鎖)。 鎖定持續時間
  • 鎖定會一直持續到事務結束(提交或回滾)。 死鎖風險
  • 使用 FOR UPDATE 時,如果兩個事務嘗試鎖定對方已經鎖定的行,可能會導致死鎖。 使用限制
  • FOR UPDATE 只能在支持行級鎖的存儲引擎(如 InnoDB)上使用。
  • 要使用 FOR UPDATE,必須開啓一個事務(使用 BEGINSTART TRANSACTION)。

例如:

BEGIN;

SELECT * FROM your_table WHERE condition FOR UPDATE;

UPDATE your_table SET column = value WHERE condition;

COMMIT;

上面的例子中,SELECT ... FOR UPDATE 語句會鎖定所有滿足 WHERE condition 的行,然後 UPDATE 語句可以安全地對這些行進行修改。

請記住,使用 FOR UPDATE 時要謹慎,因爲它可能會影響併發性和性能。只在必要時使用它,並確保事務儘可能短且高效。

2.3 原因總結

如果內配箱子涉及到相同的內配單,且存在箱子包含多個內配單,且先於其他箱子的事務(只包含一個內配單)執行 for update 的SQL語句,那麼後邊的事務會出現不會被阻塞的問題。

如果多個箱子都包含相同內配單號,且只有一個,那麼多個事務同時執行 for update 語句時就會阻塞(正確)。 一句話描述問題,執行 for update SQL語句時,想要達到多個事務阻塞效果,必須兩個事務執行select...for update 數據的查詢結果一樣。

三、解決方案

1、針對箱子入庫回傳增加分佈式鎖方案流程

  1. 箱子入庫回傳時,先查詢箱子對應的內配單號
  2. 然後將內配單按照升序排序,分別設置redis分佈式鎖
  3. 全部加鎖成功之後,處理業務
  4. 如果任何一個內配單加鎖失敗,說明被其他箱子加鎖了,拋出異常,重試

優點:僅僅改動箱子入庫回傳這塊代碼 缺點:需要開發代碼,上線測試

2、修改 for update 的SQL,增加獨立的加鎖語句

修改邏輯:

  1. 將箱子對應的內配單ID 進行 for update的查詢,且不加其他條件,確保可以其他可以鎖定住其他事務的查詢
select
  c.id as id
from
  table_c c
where
  c.id in (xxx, xxx) for
update
  1. 再查詢對應的已完成的箱子信息
select
  c.id as id
from
  table_c c
where
  c.id in (xxx, xxx)
  and not exists (
    select
      1
    from
      table_s s,
      table_b b
    where
      s.chuku_id = c.id
      and s.box_id = b.id
      and b.work_statue != '4'
  )

優點:不用增加分佈式鎖 缺點:增加了兩次數據庫查詢,一次是利用 for update 語句負責加鎖,另一次是查詢滿足條件的數據,可能會影響處理效率。

參考: https://dogslee.top/2021/08/25/%E8%AE%BE%E7%BD%AEMySQL%E7%9A%84%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/ https://www.51cto.com/article/745157.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章