MySQL 在 SELECT 死锁多人游戏中插入
MySQL INSERT on SELECT Deadlock multiplayer game
- MySQL版本:5.7
- 存储引擎:InnoDB
- 让您了解这是一个纸牌游戏(在 nodejs 中)。在这种情况下,一个 9 人 table 游戏。每个玩家可以同时玩 4 个 table 游戏。
最近我遇到了反复出现的死锁问题,例如 1K 并发用户每分钟 1 到 5 次。下面的查询平均每分钟和每个用户调用 2 次。
- 用户想玩
- 服务器总是在第一个可用的 table 游戏中找到第一个空位(座位)。
我不知道如何解决这个问题,因为构建此查询是为了处理 2 个以上不同玩家(或同一玩家 2 次以上)执行插入查询时可能出现的竞争条件。
当然,我可以通过唯一键处理竞争条件,但在 100 多个用户搜索空闲位置的高峰期无法管理。可证明它将通过重复密钥导致 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, Using where; Using index; Using temporary; Using filesort
1, PRIMARY, b, , index, , id, 1, , 9, 100.00, Using where; Using index; Using join buffer (Block Nested Loop)
4, DEPENDENT SUBQUERY, 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; Using index
3, SUBQUERY, app_users_state, , ref, game_id_user_id,user_id,game_id, user_id, 4, const, 4, 100.00,
- Fiddle:
http://sqlfiddle.com/#!9/fe63f7
注意:上述查询有时会发生死锁并且:
DELETE FROM app_users_states WHERE game_id = some_id AND user_id = some_id
在对该应用程序中面临的所有死锁进行更多研究后,我发现有 2-3 种不同的类型或情况。
其中一个是 lock mode AUTO-INC waiting
因此,第一个事务锁定了自动增量 id
列并等待索引 'X',第二个事务锁定了索引 'X' 并等待 AUTO-INC 锁。
因为我需要 id
列来获取 lastInsertId
并稍后在 SELECT
中使用它,所以必须有这个自动增量列。为了解决这个问题,我在我的应用程序中创建了一个已知的 UUID
值并存储为二进制 (16) 而不是自动增量 intid
.
第二个和第三个问题与同一查询中不同或相同 table 上 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 gap before rec insert intention waiting
。在我的 INSERT
中,我在 3 个不同的 table 中做 SELECT
,而在应用程序的其他部分,还有其他一些 UPDATE
或其他 INSERT
,到处都是死锁。
为了解决部分问题,我更改了 INSERT
语句中的(部分)SELECT
s 添加了 FOR UPDATE
,因此设置了 IX lock
而不是 S lock
,防止其他查询首先获得 X lock in this record/gap
后来我意识到,我的应用程序中又出现了一个死锁。你认为呢?另一个不同的(和最新的)INSERT
SELECT
查询在其他完全不同的 tables.
从每分钟 1 到 5 个死锁到每..10 分钟或更长时间 1 个死锁。
所以,作为我个人的意见(用例),不要在同一个查询中使用 INSERT
和 SELECT
。使用程序、事务...并检查 SELECT
是否使用了正确的索引。
对不起我的英语:)
祝你好运。
更新:
更改了主要 table 中的 1 个索引涉及 de INSERT
和 SELECT
table 之一的 1 个索引。结果:到目前为止,2 小时内出现 0 个死锁
- MySQL版本:5.7
- 存储引擎:InnoDB
- 让您了解这是一个纸牌游戏(在 nodejs 中)。在这种情况下,一个 9 人 table 游戏。每个玩家可以同时玩 4 个 table 游戏。
最近我遇到了反复出现的死锁问题,例如 1K 并发用户每分钟 1 到 5 次。下面的查询平均每分钟和每个用户调用 2 次。
- 用户想玩
- 服务器总是在第一个可用的 table 游戏中找到第一个空位(座位)。
我不知道如何解决这个问题,因为构建此查询是为了处理 2 个以上不同玩家(或同一玩家 2 次以上)执行插入查询时可能出现的竞争条件。
当然,我可以通过唯一键处理竞争条件,但在 100 多个用户搜索空闲位置的高峰期无法管理。可证明它将通过重复密钥导致 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, Using where; Using index; Using temporary; Using filesort
1, PRIMARY, b, , index, , id, 1, , 9, 100.00, Using where; Using index; Using join buffer (Block Nested Loop)
4, DEPENDENT SUBQUERY, 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; Using index
3, SUBQUERY, app_users_state, , ref, game_id_user_id,user_id,game_id, user_id, 4, const, 4, 100.00,
- Fiddle: http://sqlfiddle.com/#!9/fe63f7
注意:上述查询有时会发生死锁并且:
DELETE FROM app_users_states WHERE game_id = some_id AND user_id = some_id
在对该应用程序中面临的所有死锁进行更多研究后,我发现有 2-3 种不同的类型或情况。
其中一个是 lock mode AUTO-INC waiting
因此,第一个事务锁定了自动增量 id
列并等待索引 'X',第二个事务锁定了索引 'X' 并等待 AUTO-INC 锁。
因为我需要 id
列来获取 lastInsertId
并稍后在 SELECT
中使用它,所以必须有这个自动增量列。为了解决这个问题,我在我的应用程序中创建了一个已知的 UUID
值并存储为二进制 (16) 而不是自动增量 intid
.
第二个和第三个问题与同一查询中不同或相同 table 上 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 gap before rec insert intention waiting
。在我的 INSERT
中,我在 3 个不同的 table 中做 SELECT
,而在应用程序的其他部分,还有其他一些 UPDATE
或其他 INSERT
,到处都是死锁。
为了解决部分问题,我更改了 INSERT
语句中的(部分)SELECT
s 添加了 FOR UPDATE
,因此设置了 IX lock
而不是 S lock
,防止其他查询首先获得 X lock in this record/gap
后来我意识到,我的应用程序中又出现了一个死锁。你认为呢?另一个不同的(和最新的)INSERT
SELECT
查询在其他完全不同的 tables.
从每分钟 1 到 5 个死锁到每..10 分钟或更长时间 1 个死锁。
所以,作为我个人的意见(用例),不要在同一个查询中使用 INSERT
和 SELECT
。使用程序、事务...并检查 SELECT
是否使用了正确的索引。
对不起我的英语:)
祝你好运。
更新:
更改了主要 table 中的 1 个索引涉及 de INSERT
和 SELECT
table 之一的 1 个索引。结果:到目前为止,2 小时内出现 0 个死锁