Mysql select ... for update deadlock

时间:2016-11-25 05:09:25

标签: java mysql innodb deadlock

我正在使用mysql(innodb作为引擎)处理Web应用程序。 我有几个表,包括'用户','任务'任务_历史'。

  • '用户'具有属性:id(主键),帐户,密码,分数等
  • '任务'具有属性:id(主键),score,user_id等
  • ' task_histories'具有属性:id(主键),task_id,user_id,cancelled等

现在我有一个简单的逻辑:如果用户完成任务,那么我需要将相应的分数('任务')添加到他的旧分数('用户')。所以我有这样的java代码:

public class TaskHistoryHandler extends SyncableHandler {
    // ignore other methods or fields
    // syncableController is a field in superclass and responsible for  
    // dealing with Mybatis mappers

    @Override
    public SyncableDO insert(TaskHistoryDO taskhistory, PrincipalDO auth, long taskId) {
        taskHistory = syncableController.insert(TaskHistoryDO.class, taskHistory);
        if(!taskHistory.isFresh()) {
            return taskHistory; // already insert, then return directly
        }
        if(!taskHistory.isCanceled()) {
            TaskDO task = syncableController.getById(TaskDO.class, taskId);
            UserMapper userMapper = syncableControlle.getSqlSession().getMapper(UserMapper.class);
            UserDO user = userMapper.getUserWithLock(auth.getUserId());
            user.setScore(user.getScore() + task.getScore());
            userMapper.updateUserScore(user);
        }

    return taskHistory;
    } 
}

另一方面,我有一个基于Mybatis的UserMapper类:

public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{userId} FOR UPDATE")
    @ResultMap("user")
    UserDO getUserWithLock(@Param("userId") long userId);

    @Select("UPDATE users SET score = #{score} WHERE id=#{id}")
    int updateUserScore(UserDO user);
}

在处理http请求的spring控制器中调用TaskHistoryHandler的方法。此外,sqlsession具有范围' WebApplicationContext.SCOPE_REQUEST',并且在每个http请求之后以及服务器返回响应之前完成提交。

在本地测试期间没有问题,但在服务器上时不时会发生死锁。日志如下

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
### The error may exist in im/yon/playtask/model/mapper/UserMapper.java (best guess)
### The error may involve im.yon.playtask.model.mapper.UserMapper.getUserWithLock-Inline
### The error occurred while setting parameters
### SQL: SELECT * FROM users WHERE id = ? FOR UPDATE
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

我的问题出现了:

  1. 导致死锁的原因是什么? (我不知道当两个交易相互等待时,情况会如何发生?我的情况下会发布锁定)
  2. 是否可以避免死锁但保证用户的分数与客户端数据库中的分数一致?有任何改进代码逻辑的建议吗?
  3. 谢谢!

    更新 这是innodb的状态:

    2016-11-23 07:01:40 7f2aa0ac2700
    *** (1) TRANSACTION:
    TRANSACTION 126179072, ACTIVE 0 sec starting index read
    mysql tables in use 1, locked 1
    LOCK WAIT 7 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 1
    MySQL thread id 4990655, OS thread handle 0x7f2aa1557700, query id 553511517 10.105.39.112 playtask statistics
    SELECT * FROM users WHERE id = 41864 FOR UPDATE
    *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
    RECORD LOCKS space id 53 page no 466 n bits 168 index `PRIMARY` of table `playtask`.`users` trx id 126179072 lock_mode X locks rec but not gap waiting
    Record lock, heap no 43 PHYSICAL RECORD: n_fields 18; compact format; info bits 0
     0: len 8; hex 800000000000a388; asc         ;;
     1: len 6; hex 000007855671; asc     Vq;;
     2: len 7; hex 220000054a1d9d; asc ""   J  ;;
     3: len 5; hex 9999ea055c; asc     \;;
     4: len 5; hex 999aed705e; asc    p^;;
     5: len 1; hex 80; asc  ;;
     6: len 7; hex 73696e61726f75; asc sinarou;;
     7: len 30; hex 613732313261356639633664373330623464353536373934306336333730; asc a7212a5f9c6d730b4d5567940c6370; (total 56 bytes);
     8: len 16; hex 3934373631393439394071712e636f6d; asc 947619499@qq.com;;
     9: len 4; hex 7fffbf32; asc    2;;
     10: len 4; hex 80000000; asc     ;;
     11: SQL NULL;
     12: len 7; hex 73696e61726f75; asc sinarou;;
     13: len 4; hex 80000000; asc     ;;
     14: len 4; hex 80000000; asc     ;;
     15: len 4; hex 80000000; asc     ;;
     16: len 4; hex 80000000; asc     ;;
     17: SQL NULL;
    
    *** (2) TRANSACTION:
    TRANSACTION 126179073, ACTIVE 0 sec starting index read
    mysql tables in use 1, locked 1
    7 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 1
    MySQL thread id 4989467, OS thread handle 0x7f2aa0ac2700, query id 553511519 10.105.39.112 playtask statistics
    SELECT * FROM users WHERE id = 41864 FOR UPDATE
    *** (2) HOLDS THE LOCK(S):
    RECORD LOCKS space id 53 page no 466 n bits 168 index `PRIMARY` of table `playtask`.`users` trx id 126179073 lock mode S locks rec but not gap
    Record lock, heap no 43 PHYSICAL RECORD: n_fields 18; compact format; info bits 0
     0: len 8; hex 800000000000a388; asc         ;;
     1: len 6; hex 000007855671; asc     Vq;;
     2: len 7; hex 220000054a1d9d; asc ""   J  ;;
     3: len 5; hex 9999ea055c; asc     \;;
     4: len 5; hex 999aed705e; asc    p^;;
     5: len 1; hex 80; asc  ;;
     6: len 7; hex 73696e61726f75; asc sinarou;;
     7: len 30; hex 613732313261356639633664373330623464353536373934306336333730; asc a7212a5f9c6d730b4d5567940c6370; (total 56 bytes);
     8: len 16; hex 3934373631393439394071712e636f6d; asc 947619499@qq.com;;
     9: len 4; hex 7fffbf32; asc    2;;
     10: len 4; hex 80000000; asc     ;;
     11: SQL NULL;
     12: len 7; hex 73696e61726f75; asc sinarou;;
     13: len 4; hex 80000000; asc     ;;
     14: len 4; hex 80000000; asc     ;;
     15: len 4; hex 80000000; asc     ;;
     16: len 4; hex 80000000; asc     ;;
     17: SQL NULL;
    
    *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
    RECORD LOCKS space id 53 page no 466 n bits 168 index `PRIMARY` of table `playtask`.`users` trx id 126179073 lock_mode X locks rec but not gap waiting
    Record lock, heap no 43 PHYSICAL RECORD: n_fields 18; compact format; info bits 0
     0: len 8; hex 800000000000a388; asc         ;;
     1: len 6; hex 000007855671; asc     Vq;;
     2: len 7; hex 220000054a1d9d; asc ""   J  ;;
     3: len 5; hex 9999ea055c; asc     \;;
     4: len 5; hex 999aed705e; asc    p^;;
     5: len 1; hex 80; asc  ;;
     6: len 7; hex 73696e61726f75; asc sinarou;;
     7: len 30; hex 613732313261356639633664373330623464353536373934306336333730; asc a7212a5f9c6d730b4d5567940c6370; (total 56 bytes);
     8: len 16; hex 3934373631393439394071712e636f6d; asc 947619499@qq.com;;
     9: len 4; hex 7fffbf32; asc    2;;
     10: len 4; hex 80000000; asc     ;;
     11: SQL NULL;
     12: len 7; hex 73696e61726f75; asc sinarou;;
     13: len 4; hex 80000000; asc     ;;
     14: len 4; hex 80000000; asc     ;;
     15: len 4; hex 80000000; asc     ;;
     16: len 4; hex 80000000; asc     ;;
     17: SQL NULL;
    
    *** WE ROLL BACK TRANSACTION (2)
    

2 个答案:

答案 0 :(得分:0)

首先,你的两个Sql不在一个事务中,第一个sql锁定行进行更新,第二个sql想要更新它。这不是正确的方法,可能导致死锁。您应该通过代码或其他方式考虑开放交易。

只需要更改sql getUserWithLock并删除for update,这样可以正常工作(不是在并行情况下)。

为什么导致死锁,你可以参考show engine innodb status

答案 1 :(得分:0)

在MySQL中不要使用FOR UPDATE,在多线程环境中会遇到死锁错误。如果您希望列值保持一致。只需先执行UPDATE,然后执行SELECT即可获取列值。这样可以避免死锁问题。

坏:

START TRANSACTION
SELECT value FROM table WHERE id='a' FOR UPDATE
UPDATE table SET value=value+1 WHERE id='a'
COMMIT

好:

START TRANSACTION
UPDATE table SET value=value+1 WHERE id='a'
SELECT value FROM table WHERE id='a'
COMMIT