MySQL INSERT on SELECT 死锁多人游戏

标签 mysql database deadlock database-deadlocks

  • MySQL版本:5.7
  • 存储引擎:InnoDB
  • 让您了解这是一个纸牌游戏(在 nodejs 中)。在这种情况下是 9 人桌面游戏。每个玩家可以同时玩 4 个 table 上游戏。

最近我遇到了反复出现的死锁问题,比如在 1K 并发用户的情况下每分钟出现 1 到 5 次死锁。下面的查询平均每分钟和每个用户调用 2 次。

  • 用户想玩。
  • 服务器总是在第一个可用的桌面游戏中找到第一个空位(座位)。

我不知道如何解决这个问题,因为构建此查询是为了处理 2 个以上不同玩家(或同一玩家 2 次以上)执行插入查询时可能出现的竞争条件。

当然,我可以通过唯一键处理竞争条件,但在 100 多个用户搜索免费位置的高峰期无法管理。可证明它将通过重复 key 导致 91+ 次拒绝。


LATEST DETECTED DEADLOCK
------------------------

*** (1) TRANSACTION:
TRANSACTION 100539324, ACTIVE 0 sec inserting
mysql tables in use 5, locked 5
LOCK WAIT 23 lock struct(s), heap size 3520, 111 row lock(s), undo log entries 1
MySQL thread id 6782, OS thread handle 2460, query id 138188765 localhost 127.0.0.1 root Creating sort index
INSERT INTO app_tables_players (user_id, game_id, seat_number, username, state, folded) SELECT 597, a.id, b.id AS seat_number, 'some_username', 'temp', false FROM (SELECT id FROM app_tables_games WHERE table_id = 6 AND seats_total > seats_taken AND id NOT IN (SELECT game_id FROM app_users_state WHERE user_id = 597)) AS a, `app_temp_players_9` AS b WHERE b.id NOT IN (SELECT seat_number FROM app_tables_players WHERE game_id = a.id) ORDER BY a.id ASC LIMIT 1

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 75 page no 51 n bits 1120 index seat_number_game_id of table `nodejs`.`app_tables_players` trx id 100539324 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 964 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 04; asc  ;;
 1: len 4; hex 0008d8f6; asc     ;;
 2: len 4; hex 006b437b; asc  kC{;;

*** (2) TRANSACTION:
TRANSACTION 100539325, ACTIVE 0 sec setting auto-inc lock, thread declared inside InnoDB 4743
mysql tables in use 5, locked 5
22 lock struct(s), heap size 3520, 151 row lock(s)
MySQL thread id 6702, OS thread handle 11428, query id 138188764 localhost 127.0.0.1 root Creating sort index
INSERT INTO app_tables_players (user_id, game_id, seat_number, username, state, folded) SELECT 613, a.id, b.id AS seat_number, 'some_username2', 'tmp', false FROM (SELECT id FROM app_tables_games WHERE table_id = 14 AND seats_total > seats_taken AND id NOT IN (SELECT game_id FROM app_users_state WHERE user_id = 613)) AS a, `app_temp_players_9` AS b WHERE b.id NOT IN (SELECT seat_number FROM app_tables_players WHERE game_id = a.id) ORDER BY a.id ASC LIMIT 1

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 75 page no 51 n bits 1120 index seat_number_game_id of table `nodejs`.`app_tables_players` trx id 100539325 lock mode S locks gap before rec
Record lock, heap no 18 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 06; asc  ;;
 1: len 4; hex 0008d6e4; asc     ;;
 2: len 4; hex 006b5a47; asc  kZG;;

Record lock, heap no 97 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 04; asc  ;;
 1: len 4; hex 0008d97e; asc    ~;;
 2: len 4; hex 006b5d1e; asc  k] ;;

Record lock, heap no 160 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 05; asc  ;;
 1: len 4; hex 0008d93e; asc    >;;
 2: len 4; hex 006b4cb3; asc  kL ;;

Record lock, heap no 181 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 04; asc  ;;
 1: len 4; hex 0008d92f; asc    /;;
 2: len 4; hex 006b5c0b; asc  k\ ;;

Record lock, heap no 267 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 04; asc  ;;
 1: len 4; hex 0008d9b2; asc     ;;
 2: len 4; hex 006b5b9a; asc  k[ ;;

Record lock, heap no 272 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 05; asc  ;;
 1: len 4; hex 0008d999; asc     ;;
 2: len 4; hex 006b5d43; asc  k]C;;

Record lock, heap no 307 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 04; asc  ;;
 1: len 4; hex 0008da33; asc    3;;
 2: len 4; hex 006b6182; asc  ka ;;

Record lock, heap no 346 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 04; asc  ;;
 1: len 4; hex 0008da38; asc    8;;
 2: len 4; hex 006b5fbd; asc  k_ ;;

Record lock, heap no 530 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 05; asc  ;;
 1: len 4; hex 0008d9b2; asc     ;;
 2: len 4; hex 006b544e; asc  kTN;;

Record lock, heap no 556 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 05; asc  ;;
 1: len 4; hex 0008d97e; asc    ~;;
 2: len 4; hex 006b62f3; asc  kb ;;

Record lock, heap no 629 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 04; asc  ;;
 1: len 4; hex 0008d999; asc     ;;
 2: len 4; hex 006b5f41; asc  k_A;;

Record lock, heap no 804 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 05; asc  ;;
 1: len 4; hex 0008da33; asc    3;;
 2: len 4; hex 006b60bc; asc  k` ;;

Record lock, heap no 906 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 04; asc  ;;
 1: len 4; hex 0008d93e; asc    >;;
 2: len 4; hex 006b4988; asc  kI ;;

Record lock, heap no 962 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 04; asc  ;;
 1: len 4; hex 0008d928; asc    (;;
 2: len 4; hex 006b5b03; asc  k[ ;;

Record lock, heap no 964 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 04; asc  ;;
 1: len 4; hex 0008d8f6; asc     ;;
 2: len 4; hex 006b437b; asc  kC{;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
TABLE LOCK table `nodejs`.`app_tables_players` trx id 100539325 lock mode AUTO-INC waiting
*** WE ROLL BACK TRANSACTION (2)

导致问题的查询:

INSERT INTO app_tables_players
    (user_id, 
    game_id, 
    seat_number) 
SELECT
    597, 
    a.id, 
    b.id AS seat_number
FROM 
    (SELECT id 
    FROM
        app_tables_games 
    WHERE
        table_id = 14
        AND seats_total > seats_taken 
        AND id NOT IN (
            SELECT game_id 
            FROM   app_users_state 
            WHERE  user_id = 597
        )
    ) AS a, 
    app_temp_players_9 AS b 
WHERE
    b.id NOT IN (
        SELECT seat_number 
        FROM   app_tables_players 
        WHERE  game_id = a.id
    ) 
ORDER BY a.id ASC 
LIMIT 1;

  • 解释查询:

id、select_type、table、partitions、type、possible_keys、key、key_len、ref、rows、filtered、Extra

1, INSERT, app_tables_players, , ALL, , , , , , ,

1, PRIMARY, app_tables_games, , ref, table_id,table_id_seats_taken_seats_total, table_id_seats_taken_seats_total, 4, const, 24, 33.33, 使用哪里;使用索引;使用临时的;使用文件排序

1, PRIMARY, b, , index, , id, 1, , 9, 100.00, 使用哪里;使用索引;使用连接缓冲区( block 嵌套循环)

4, 依赖子查询, app_tables_players, , index_subquery, seat_number_game_id,game_id_user_id,game_id,seat_number,game_id_state, seat_number_game_id, 6, func,func, 2, 100.00, Using where;使用索引

3、SUBQUERY、app_users_state、ref、game_id_user_id、user_id、game_id、user_id、4、const、4、100.00、

注意:有时上述查询会发生死锁,并且:

DELETE FROM app_users_states WHERE game_id = some_id AND user_id = some_id

最佳答案

在对该应用程序中面临的所有死锁进行更多研究后,我发现有 2-3 种不同的类型或情况。

其中一个是锁定模式 AUTO-INC 等待

因此,第一个事务锁定了自动增量 id 列并等待索引“X”,第二个事务锁定了索引“X”并等待 AUTO-INC 锁。

因为我需要 id 列来获取 lastInsertId 并稍后在 SELECT 中使用它,所以必须有这个自动增量柱子。为了解决这个问题,我在我的应用程序中创建了一个已知的 UUID 值并将其存储为二进制 (16) 而不是自动增量 intid

第二个和第三个问题与同一查询中不同表或同一表中SELECT 上的INSERT 有关。喜欢(简单的例子);

INSERT INTO tableA some_colX(SELECT some_colY FROM tableA WHERE some_colZ = 1 LIMIT 1)

SELECT 使用了 lock mode S locks gap before rec 但是 INSERT 需要一个 lock_mode X locks before rec插入意图等待。在我的 INSERT 中,我在 3 个不同的表中执行 SELECT,而在应用程序的其他部分,还有其他一些 UPDATE 或其他 INSERT,所以到处都是死锁。

为了解决其中的一部分,我更改了 INSERT 语句中的(部分)SELECT,添加了 FOR UPDATE ,设置了一个IX 锁 而不是 S 锁,防止其他查询首先获得此记录/间隙中的 X 锁

后来我意识到,我的应用程序中存在另一个死锁。你认为呢?另一个不同的(和最新的)INSERT on SELECT query in other completely different tables.

从每分钟 1 到 5 个死锁到每 ..10 分钟或更长时间 1 个死锁。

因此,作为我个人的意见(用例),不要在同一个查询中使用 INSERTSELECT。使用过程、事务...并检查 SELECT 是否使用了正确的索引。

对不起我的英语:)

祝你好运。

更新:

将主表中的 1 个索引更改为 de INSERT 和其中一个 SELECT 表中的 1 个索引。结果:到目前为止,2 小时内出现 0 个死锁

关于MySQL INSERT on SELECT 死锁多人游戏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47741982/

相关文章:

php - MySQL默认值不正确

php - php和mysql注册系统

java - 无法将数据保存到数据库

mysql - JBOSS 和 MySql

ruby - 线程死锁

从另一个 python 脚本调用 python 脚本时,Python 日志记录挂起

mysql - 使用两个参数计算 DISTINCT 语句中的多次出现?

mysql - MySQL中Rank和DenseRank的实现

java - Hibernate搜索查询,受子类限制?

mysql - golang中查询mysql时发生死锁错误,不是在查询段中而是在out of "rows.Next()"循环之后