使用并发连接从 table 中获取

Fetching from table with concurrent connections

我们有一款游戏,用户可以登录并玩游戏,该游戏仅限 6 人一组,当 6 名用户加入一组时游戏开始。

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

  1. 如果组中没有用户(第一个进入游戏的用户)- 我们生成一个 random_number 并将其插入新用户
  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 个用户。

问题是当并发连接调用存储过程时,所有存储过程都会获取 select 查询并获取 random_number,因此它只会为所有更新相同的 random_number用户。 任何人都可以帮助我如何停止存储过程读取数据,直到先前的存储过程更新 table 和 commit

您的程序存在一些问题:

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

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

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

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

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

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

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

最后一期:

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

这通常是 , as temp_id does not have a deterministic value (e.g. which of the up to 6 values for temp_id per random_number do you mean)? MySQL allows this only if you disable only_full_group_by(您不应该这样做)。修复可以例如是使用 order by min(temp_id) (取决于你想要实现的目标)。另一方面,如果您添加 team-table,您可以(正确地)使用 group by team_id having .... order by team_id.