一致的数据库并发读写

时间:2019-03-28 10:17:37

标签: database postgresql

我已经为一个简单的游戏编写了REST API,包括创建,加入和提交游戏的比赛。我在测试服务器时遇到问题。通过发送单个玩家(一次很多)的加入请求,服务器会将玩家加入大厅的倍增次数。

我的设计如下:

  • 在玩家表中检查玩家的字段“ playing”是否设置为true,如果是,则丢弃请求
  • 如果玩家当前不在玩,请为大厅创建新的玩家条目,并将其播放状态设置为true

我知道问题出在检查玩家当前是否在玩。每个请求都由Go中单独的goroutine运行,因此有可能每个goroutine从数据库中获取玩家的“ playing”字段等于false的信息。然后,每个鱼肉酱都会在大厅中添加同一位玩家,这是我要避免的倍数。

是否可以避免此问题,或者问题出在我的设计中?

3 个答案:

答案 0 :(得分:0)

有几种选择-值得阅读Postgres上的docs进行并发处理。

简短的版本是:确保插入/更新仅在他们以期望的状态找到数据并使用事务时才触发。

所以,像这样:

begin;

insert into lobby
select player_id, ...
from players
where player_id = $current_player
and player_id not in (select player_id from lobby);

update player
set playing = 1
where playing = 0
and player_id = $player_id;

commit;

如果其他一些进程已经将播放器标记为活动状态,则这两个SQL语句将不执行任何操作,但它们将同时触发。 这应该意味着,如果您多次尝试同时更新玩家的状态,那么只有其中之一会做任何事情。

答案 1 :(得分:0)

您的问题与Go无关,而是与数据库结构有关。

为简化解决方案,

要确保您只有一个玩家同时玩,可以为此创建一个专用表,并带有唯一的索引/约束。

如何在2列上添加唯一性,请参见以下问题:In Postgresql, Force unique on combination of two columns 在您的情况下,列将为player_idplaying

在API级别,如果玩家已经在玩游戏,则需要处理从数据库中获取的错误。

答案 2 :(得分:0)

如果您想确保同一大厅中永远不会有相同的用户两次,则应该对玩家表中这两个字段的组合添加唯一约束(假设players是链接{{ 1}}至users)。

一旦有了,就可以使用upsert

lobbies

这将创建记录-但是,如果由于INSERT INTO players (user_id, lobby_id, playing) VALUES (12, 41, 1) ON CONFLICT name_of_your_constraint UPDATE players.playing = 1; user的组合已经存在而无法实现,则会将用户标记为正在播放。