首页 > 解决方案 > Postgres 对房间内用户限制的设计解决方案(竞争条件)

问题描述

我有一个聊天应用程序,其中用户可以随时自由加入/离开。房间一次限制为 8 位用户。

为简化起见,我现在有这种关系:

用户 ->(多对一)-> 房间

我正在通过查询来检查房间是否已满

SELECT COUNT(*) FROM users WHERE room_id = x

在插入用户之前,这在正常情况下有效。

但是,如果每个人都同时加入,那么它就会进入竞争状态,并且会绕过限制。我应该如何解决这个问题?Postgres 适合这种操作吗?

标签: sqlnode.jspostgresql

解决方案


虽然不想不友善,但之前的答案远非最佳。

虽然你可以做到这一点...

LOCK TABLE users IN ACCESS EXCLUSIVE MODE;

这显然是相当沉重的,并且会阻止对用户表的所有更新,无论是否与房间更改有关。

一种更轻松的方法是只锁定您关心的数据。

-- Move user 456 from room 122 to room 123
BEGIN;

SELECT true FROM rooms WHERE id = 123 FOR UPDATE;
SELECT true FROM users WHERE id = 456 AND room_id = 122 FOR UPDATE;
-- If either of the above failed to return a row, the starting condition of your database is not what you thought. Rollback

SELECT count(*) FROM users WHERE room_id = 123;
-- check count looks ok

UPDATE users SET room_id = 123 WHERE id = 456;

COMMIT;

这将锁定两个关键项目:

  1. 搬去的新房间。
  2. 用户在房间之间移动。

既然你不关心人们被搬出房间,这应该足够了。

如果两个独立的连接试图同时将不同的人移动到同一个房间,一个将被迫等待,直到另一个事务提交或回滚并释放它的锁。如果两个独立的连接尝试更新同一个人,同样的情况也会发生。

至于其他答案

当然,在表被锁定期间,如果你的应用程序中的某个线程试图写用户,它会返回一个错误。

不,不会的。除非您持有锁足够长的时间以超时,或者尝试使用自己的锁并告诉它不要等待。

我认为您可能应该同步调用应用程序的访问和写入

关系数据库已被明确设计为处理来自多个客户端的并发访问。这是他们无可争辩的擅长的事情之一。如果您已经在使用 RDBMS 并实现自己的并发控制,那么您要么处于特殊情况,要么没有正确使用您的 RDBMS。


推荐阅读