关于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

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