繁体   English   中英

Mysql select ... for update 死锁

[英]Mysql select … for update deadlock

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

  • 'users' 具有以下属性:id(主键)、帐户、密码、分数等。
  • 带有属性的“任务”:id(主键)、分数、user_id 等。
  • 'task_history' 具有以下属性:id(primary key)、task_id、user_id、canceled 等。

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

public class TaskHistoryHandler extends SyncableHandler {
    // ignore other methods or fields
    // syncableController is a field in the 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

我的问题来了:

  1. 导致死锁的原因是什么? (我不知道在我的情况下,当两个事务等待彼此的锁被释放时,会发生什么情况)
  2. 是否有可能避免死锁但保证用户的分数与客户端数据库中的分数一致? 有什么建议可以改进代码逻辑吗?

谢谢!

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

不要在 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

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

只需更改 sql getUserWithLock并删除 for 更新,这将正常工作(不是在并行情况下)。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM