简体   繁体   中英

Mysql select … for update deadlock

I'm working on a web application using mysql (innodb as engine). I have several tables including 'users', 'tasks', 'task_histories'.

  • 'users' with attributes: id(primary key), account, password, score, etc.
  • 'tasks' with attributes: id(primary key), score, user_id, etc.
  • 'task_histories' with attributes: id(primary key), task_id, user_id, canceled, etc.

Now I have a logic simply: if a user finishes a task, then I need to add the corresponding score('tasks') to his old score('users'). So I have java code like this:

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;
    } 
}

On the other hand, I have a UserMapper class based on Mybatis:

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's method is invoked in a spring controller handling HTTP request. Besides, the sqlsession has the scope 'WebApplicationContext.SCOPE_REQUEST', and the commit is done after each HTTP request and before the server returns the response.

During local test there's no problem, but on the server deadlock occurs from time to time. The log is as follows

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

Here comes my question:

  1. What causes the deadlock? (I don't know how a situation can occur when two transactions wait for one another's lock to be released in my case)
  2. Is it possible to avoid deadlock but guarantee the user's score is consistent with that in the client's database? Any suggestions to improve the code logic?

Thanks!

Update: Here's the innodb status:

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)

Don't use FOR UPDATE in MySQL, You will encounter Deadlock error in multi-thread environment. If you want the column value be consistent. Just execute UPDATE first, then exeucte SELECT to get the column value. It will be avoid the Deadlock problem.

Bad:

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

Good:

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

First of all, your two Sql are not in a transaction, the first sql locks the row for update ,and the second sql want to update that. This is not the right way and may cause dead lock. You should consider open transaction by code or some other way.

Simply you need to change the sql getUserWithLock and remove the for update, this will work fine( not in parallel situation).

Why cause the deadlock, you can refer to show engine innodb status .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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