我正在使用mysql(innodb作为引擎)处理Web应用程序。 我有几个表,包括'用户','任务'任务_历史'。
现在我有一个简单的逻辑:如果用户完成任务,那么我需要将相应的分数('任务')添加到他的旧分数('用户')。所以我有这样的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
我的问题出现了:
谢谢!
更新 这是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)
答案 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