2016-11-25 76 views
3

我正在使用mysql(innodb作爲引擎)的Web應用程序上工作。 我有幾個表,包括'用戶','任務','task_histories'。Mysql select ...更新死鎖

  • '用戶' 與屬性:id(主鍵),賬號,密碼,分數等
  • '任務' 與屬性:id(主鍵),得分,USER_ID等
  • 「task_histories」與屬性:id(主鍵),TASK_ID,USER_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請求調用。此外,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 [email protected];; 
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 [email protected];; 
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 [email protected];; 
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

向我們展示事務中的所有SQL語句。我期望他們是這樣的:'BEGIN; SELECT ... FOR UPDATE; (等等); COMMIT;' –

回答

0

首先,你的兩個SQL是不是在一個事務中,第一個SQL鎖定該行的更新,而第二SQL要更新那。這不是正確的方式,可能會導致死鎖。您應該考慮通過代碼或其他方式進行未完成交易。

只需要更改sql getUserWithLock並刪除for update,這將工作正常(不是在並行情況下)。

爲什麼造成死鎖,可以參考show engine innodb status

+0

嗨!實際上,對於每個http請求,只有一個sqlsession和一個事務在配置文件中配置。如果我刪除'更新',一個用戶可能會添加兩次不是預期的分數。我在我的問題中添加innodb信息。我不明白爲什麼交易2持有S鎖? – jcwang

+0

是否有一個名爲'playtask'的表?根據innodb日誌,「playtask」是僵局的原因。 – FisherMartyn

+0

不,這確實是數據庫名稱。問題在於表'playtask''''users'。看來事務1正在等待事務2釋放S鎖,以便它可以獲得X鎖?但我不明白爲什麼有一個S鎖。 – jcwang