【问题标题】:Mysql concurrent updates on a row leading to deadlockMysql对一行的并发更新导致死锁
【发布时间】:2017-09-22 23:00:01
【问题描述】:

使用 mysql 5.7 和存储引擎作为 innodb。我有一个存储产品信息的表。该表看起来像这样,在 productId 上具有唯一键

| Field     | Type         | Null | Key | Default           | Extra                       |
+-----------+--------------+------+-----+-------------------+-----------------------------+
| id        | bigint(20)   | NO   | PRI | NULL              | auto_increment              |
| productId | varchar(50)  | NO   | UNI | NULL              |                             |
| seller    | varchar(100) | NO   | MUL | NULL              |                             |
| updatedAt | timestamp    | NO   | MUL | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| status    | varchar(100) | NO   | MUL | NULL              |                             |
| data      | longtext     | NO   |     | NULL              |                             |
+-----------+--------------+------+-----+-------------------+-----------------------------+

我通过连接到这个 mysql 的 java 应用程序有两个操作:
1. 如果 productId 的新传入事件(包含有关产品更改的信息)的版本高于现有事件,则需要插入它们。版本在我的数据列中存储为 json blob
2. 更新productId的行来改变状态。

我的隔离级别是已提交读。 我尝试了两种方法,但都导致了死锁:

方法一:

Transaction1 starts
 Insert ignore into products where productId='X' values();  // Takes a S lock on the row 
 select * from products where productId='X' for update ;    // Take a X lock on the row to prevent new writes and compare the incoming event with the current event
 Insert into products values on duplicate key update values // insert into row and on duplicate key update values
commit

并发更新将打开另一个事务:

Transaction2 starts
 select * from products where productId='X' for update ;    // Take a X lock on the row to prevent new writes and compare the incoming event with the current event
 Insert into products values on duplicate key update values // insert into row and on duplicate key update values
commit;

这会导致以下情况出现死锁:
1. 事务 1 - 插入忽略语句在行上取得了 S 锁。
2. 事务 2 - Select for update 语句正在等待对行进行 X 锁定。
3. 事务 1 - Select for update 语句尝试在行上获取 X 锁。

这会导致死锁,因为事务 1 持有 S 锁,而事务 2 正在等待获取 X 锁,当事务 1 尝试获取 X 锁时,它会导致死锁。

方法二:

Transaction 1 starts: 
 select * from products where productId='X' for update ; // If a row exists then it will be locked else I know it does not exist
 Insert ignore into products where productId='X' values(); 
commit

Transaction 2 starts:
 select * from products where productId='X' for update ; // If a row exists then it will be locked else I know it does not exist
commit

这会导致以下情况出现死锁:
1. 事务 1 - Select for update 语句对行进行 X 锁定。
2. 事务 2 - Select for update 语句正在等待对该行进行 X 锁定。
3. 事务 1 - 插入忽略语句尝试在行上获取 S 锁,但事务 1 的 X 锁已经在等待导致死锁的锁

所以,我想知道如何处理并发更新并将新事件(而不是行更新)插入到我的表中而不会导致死锁。
1. 锁定顺序应该是什么?
2. 如何确保并发更新和新行插入没有死锁。

任何帮助将不胜感激:)

【问题讨论】:

    标签: mysql transactions innodb deadlock database-deadlocks


    【解决方案1】:

    经过一些实验,我设法解决了它,核心问题是在一个事务中采用 S 和 X 锁的顺序,然后在另一个事务中采用 X 锁。基本上,一开始的 S 锁导致所有情况都出现死锁。
    因此,我将插入忽略语句移到事务之外作为第一条语句。该事务现在只需要 X 锁,这意味着其中一个事务等待另一个获取 X 锁。

    Event1 : 插入一个新事件

    result = Insert ignore into products where productId='X' values(); 
    if result == null
     return
    end
    Transaction start
     select * from products where productId='X' for update ;    // Take a X lock on the row to prevent new writes and compare the incoming event with the current event
     Insert into products values on duplicate key update values // insert into row and on duplicate key update values
    commit
    

    事件 2:更新现有事件

     Transaction start
       select * from products where productId='X' for update ; // If a row exists then it will be locked else I know it does not exist
      commit
    

    因此,这两个事件都有只竞争 X 锁的事务,这有助于我避免死锁。

    【讨论】:

    • 或者,重新应用被杀死的事务。您确实需要做好准备,因为某些事情可能会导致死锁或其他对事务致命的错误。
    • 感谢@RickJames 的建议。是的,重启交易是一种选择。但是,这是一个反复出现的模式,上述解决方案解决了这个问题。总的来说,您对死锁有什么看法,不应该避免吗?
    • 可以避免一些死锁。我同意试图避免它们。然而,我在这个论坛上发现很多人似乎认为所有的死锁都可以避免,并且花了太多时间试图这样做。很高兴您解决了自己的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多