Mysql

使用單個鍵插入單個表時出現死鎖。(Mysql)

  • March 31, 2022

我有一個簡單的遊戲伺服器。我收到的一項要求是一次處理單個玩家的單個請求。所以為了實現這一點,我決定創建一個鎖表:

CREATE TABLE `GameRoundLock` (`theKey` varchar(255) NOT NULL,
PRIMARY KEY (`theKey`))
ENGINE=InnoDB DEFAULT CHARSET=utf8;

在我處理來自任何玩家的任何請求之前,我將在該表中插入一行,如下所示:

// save and flush == acquiring the lock
GameRoundLock lock = this.gameRoundLockRepository.saveAndFlush(somePlayerKey);

  /** Play busniess logic here...  **/

// deleting == releasing the lock
this.gameRoundLockRepository.delete(lock); 

這實際上有效。

在以下場景中,整個系統按預期工作:

1) PlayerA 播放,因此插入了一個新鎖。

2)處理 playerA 請求…(需要 30 秒)

3) 當 2) 仍在執行時,PlayerA 再次播放 - 這當然會導致嘗試為他創建鎖,並且執行緒現在等待直到 2) 完成。

4) 2) 執行緒完成

5) 3) 的執行緒現在繼續

6) 完成,playerA 總共玩了幾輪 == 2。

注意id(playerKey)是一樣的。

當 playerA 嘗試另一個播放請求而第一個執行緒的處理仍然發生時,就會出現問題。這是導致奇怪死鎖的場景:

1) PlayerA 播放,因此插入了一個新鎖。

2)處理 playerA 請求…(需要 30 秒)

3) 當 2) 仍在執行時,PlayerA 再次播放 - 這當然會導致嘗試為他創建鎖,並且執行緒現在等待直到 2) 完成。

4) 當 2) 仍在執行和 3) 仍在等待時,PlayerA 再次播放 - 這當然導致嘗試為他創建鎖,並且執行緒現在等待直到 2)/3) 完成。

5) 2) 執行緒完成

6) 錯誤 - 引發死鎖!!!!!!

這是死鎖資訊:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2016-11-23 17:05:26 0x1d98
*** (1) TRANSACTION:
TRANSACTION 5493356, ACTIVE 8 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 5191, OS thread handle 5152, query id 15582091 localhost    127.0.0.1 root update
insert into GameRoundLock (theKey) values ('1,1,1,64')
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4865 page no 3 n bits 72 index PRIMARY of table      `game_server`.`gameroundlock` trx id 5493356 lock_mode X locks rec but not gap    waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
0: len 8; hex 312c312c312c3634; asc 1,1,1,64;;
1: len 6; hex 00000053d266; asc    S f;;
2: len 7; hex 790000030903d1; asc y      ;;

*** (2) TRANSACTION:
TRANSACTION 5493355, ACTIVE 12 sec inserting, thread declared inside InnoDB    1
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 5193, OS thread handle 7576, query id 15582083 localhost    127.0.0.1 root update
insert into GameRoundLock (theKey) values ('1,1,1,64')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 4865 page no 3 n bits 72 index PRIMARY of table     `game_server`.`gameroundlock` trx id 5493355 lock mode S
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info     bits 32
0: len 8; hex 312c312c312c3634; asc 1,1,1,64;;
1: len 6; hex 00000053d266; asc    S f;;
2: len 7; hex 790000030903d1; asc y      ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4865 page no 3 n bits 72 index PRIMARY of table    `game_server`.`gameroundlock` trx id 5493355 lock_mode X locks rec but not gap    waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info    bits 32
0: len 8; hex 312c312c312c3634; asc 1,1,1,64;;
1: len 6; hex 00000053d266; asc    S f;;
2: len 7; hex 790000030903d1; asc y      ;;

*** WE ROLL BACK TRANSACTION (2)

似乎對於第二個和第三個播放請求的鎖的兩個“close\near”插入會導致這種情況。為什麼會出現死鎖以及如何緩解它?

我猜你有以下問題。

如果發生重複鍵錯誤,則會在重複索引記錄上設置共享鎖。如果另一個會話已經擁有排他鎖,那麼如果有多個會話嘗試插入同一行,則使用共享鎖可能會導致死鎖。如果另一個會話刪除了該行,就會發生這種情況。 http://dev.mysql.com/doc/refman/5.6/en/innodb-locks-set.html

使用鎖是錯誤的方法,因為這會為每個等待的玩家在伺服器上綁定一個執行緒。如果有很多玩家,您的伺服器將很快耗盡執行緒。它也是 DOS 攻擊的載體,因為該系統讓黑客可以輕鬆地在您的伺服器上創建足夠的執行緒,所有執行緒都在等待鎖定,以關閉服務。

一個更好的方法是:

  • 插入行並立即送出事務,釋放 myslq 連接
  • 當玩家完成他的回合時,刪除該行並送出
  • 如果在前面的步驟之間,來自同一播放器的另一個客戶端嘗試插入同一行,則回复“稍後再試”,如果可能的話,給出等待延遲的估計值

這將等待執行緒從伺服器移動到客戶端,這是可擴展的。

引用自:https://dba.stackexchange.com/questions/156174