Java Mysql选择。。。用于更新死锁

Java Mysql选择。。。用于更新死锁,java,mysql,innodb,deadlock,Java,Mysql,Innodb,Deadlock,我正在使用mysql(innodb作为引擎)开发一个web应用程序。 我有几个表,包括“用户”、“任务”、“任务历史记录” 具有以下属性的“用户”:id(主键)、帐户、密码、分数等 带有属性的“任务”:id(主键)、分数、用户id等 具有以下属性的“任务历史记录”:id(主键)、任务id、用户id、已取消等 现在我有一个简单的逻辑:如果用户完成了一项任务,那么我需要将相应的分数(“任务”)添加到他的旧分数(“用户”)中。我有如下java代码: public class TaskHistory

我正在使用mysql(innodb作为引擎)开发一个web应用程序。 我有几个表,包括“用户”、“任务”、“任务历史记录”

  • 具有以下属性的“用户”:id(主键)、帐户、密码、分数等
  • 带有属性的“任务”:id(主键)、分数、用户id等
  • 具有以下属性的“任务历史记录”:id(主键)、任务id、用户id、已取消等
现在我有一个简单的逻辑:如果用户完成了一项任务,那么我需要将相应的分数(“任务”)添加到他的旧分数(“用户”)中。我有如下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);
}
TaskHistoryHandler的方法在处理http请求的spring控制器中调用。此外,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
我的问题来了:

  • 是什么导致了僵局?(在我的案例中,我不知道当两个事务等待对方的锁被释放时会出现什么情况)
  • 是否可以避免死锁,但保证用户的分数与客户端数据库中的分数一致?有什么改进代码逻辑的建议吗
  • 谢谢

    更新: 以下是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)
    

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

    只需更改sql
    getUserWithLock
    并删除for update,就可以了(不是并行情况)


    为什么会导致死锁,可以参考。

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

    坏的:

    好:


    你好事实上,对于每个http请求,只有一个sqlsession和一个事务,这是在配置文件中配置的。如果我删除“for update”,一个用户可能会添加两次分数,这是不期望的。我在问题中添加了innodb信息。我不明白为什么事务2持有S锁?是否有一个名为
    playtask
    的表?根据innodb日志,
    playtask
    是死锁的原因。不,这确实是db名称。问题在于表
    playtask
    users
    。事务1似乎正在等待事务2释放S锁,以便获得X锁?但我不明白为什么会有s锁。显示事务中的所有SQL语句。我希望它们是这样的:
    BEGIN;选择。。。更新;(等);承诺
    
    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