首页 > 解决方案 > 从具有并发连接的表中获取

问题描述

我们有一个用户可以登录并玩游戏的游戏,该游戏仅适用于 6 个用户组,当 6 个用户加入一个组时,游戏开始。

我们在 mysql 中有一个名为“temp_group”的表,其中包含 user_id 和一个 random_number 现在,我们要实现的是当用户进入游戏时,我们需要插入他的 user_id 并检查有多少用户已经在具有相同 random_number 的组中,场景如下:

  1. 如果组中没有用户(第一个进入游戏的用户) - 我们生成一个随机数并将其插入新用户
  2. 组中有用户(第 2 到第 6 个用户加入游戏) - 获取该组的 random_number 并针对该用户进行更新
  3. 如果第 7 个用户加入游戏 - 将他视为组的新用户,并按照第 1 点进行操作。

我们创建了一个存储过程,我们首先检查

SELECT count(random_number), random_number from temp_group group by random_number HAVING COUNT(random_number) < 6 order by id ASC limit 1

如果我们得到该行,我们获取 random_number 并为新用户(第 2 - 6 个用户)更新它

如果我们没有得到该行,我们生成 random_number 并针对新用户更新它。

我们的存储过程:

BEGIN
DECLARE ch_done INT DEFAULT 0;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET ch_done = 1;

            START TRANSACTION;

            SELECT count(random_number), random_number  from `temp_group` where group by random_number HAVING COUNT(random_number) < players order by temp_id ASC limit 1 into cnt, randomnumber;

            IF(ch_done = 1) THEN 
                IF(randomnumber IS NULL) OR (cnt = 0) THEN    
                    SET randomnumber = MD5(NOW());
                    SET cnt = 0;
                END IF;
            END IF;

            INSERT INTO temp_group (`user_id`,`random_number`,`no_of_players`) VALUES(userid,,randomnumber,6) ON DUPLICATE KEY UPDATE `random_number` = randomnumber, `no_of_players` = 6;
            COMMIT;
END

问题:

当我们一次测试一个用户加入游戏时,它可以正常工作。但是当我们用并发连接测试它时,相同的 random_number 被分配给超过 6 个用户。

问题是当并发连接调用存储过程时,所有存储过程都会获取选择查询并获取随机数,因此它只是为所有用户更新相同的随机数。任何人都可以帮助我如何阻止存储过程读取数据,直到上一个存储过程更新表并提交

标签: mysqlnode.jsstored-procedures

解决方案


您的程序存在一些问题:

首先,可能是您的主要问题,您select的问题是非锁定的,例如,如果两个并发会话同时读取表,它们可以获得相同的结果并根据相同的当前状态做出决定。例如,他们可能会将他们的球员添加到同一支球队,因为他们都假设目前只有 5 名未分配球员,从而导致球队 7 人。修复将是锁定读取,您可以通过添加for update

SELECT count(random_number), random_number ... into cnt, randomnumber for update; 

然后第二个查询必须等到您提交第一个会话(空表除外,这可能是也可能不是这里的问题),因此,如您所愿,它“停止存储过程读取数据直到前一个存储过程更新表并提交”

第二个问题MD5(NOW())不是唯一的。如果您有两个并发会话,则意味着它们同时运行 - 因此MD5(NOW())将返回相同的值,即使您的过程实际上打算创建一个新组。如果超过 6 名玩家在同一秒内加入,这可能只会成为问题,但即使是罕见的问题也是问题。

虽然您可能可以找到更好的随机化器,但我强烈建议您添加一个新表,其中包含有关该组的信息,以最简单的形式,例如 just team (id int primary key auto_increment)。您当前的表temp_group可能会变成类似team_user (team_id, user_id).

不是生成一个随机数来创建一个新团队(并同时为其分配一名球员),而是通过在球队表中添加一行来创建一个新球队,然后通过将球员插入到team_user桌子。这样,您就可以正确地将两个逻辑步骤(创建新团队、将用户添加到团队)分成两个实际步骤。

一个适当的、规范化的数据模型使许多数据库任务变得更容易,例如你的 teamid 的唯一性基本上自然而然地出现了,而无需做任何更多的工作(即使你需要每个团队唯一的 md5 值,你也有一个简单的方法来执行它通过向团队表添加唯一键)。还假设您想要添加有关团队的任何其他信息(例如团队名称、游戏模式、创建私人/密码保护大厅的选项,或者,正如您目前似乎存储的那样,最多 6 名玩家) - 您必须在表中多次存储它temp_group,这总是一个坏主意。

最后一个问题:

SELECT ... group by random_number ... order by temp_id

这通常是无效的,因为temp_id没有确定性值(例如,您的意思是最多 6 个值中的哪一个)temp_idrandom_numberMySQL 仅在您禁用时才允许这样做only_full_group_by(您不应该这样做)。例如,可以使用一个修复程序order by min(temp_id)(取决于您想要实现的目标)。另一方面,如果添加team-table,则可以(正确)使用group by team_id having .... order by team_id.


推荐阅读