我的应用程序中有很多地方,用户正在做一些在每个用户和命名空间基础上应该是非并发的事情。所以这意味着:
- 两个用户可以同时按下同一个按钮
- 在第一次按下完成之前,用户无法第二次按下同一按钮
- 一个用户可以同时按下不同的按钮
我尝试过哪些解决方案:
- 用户 uuid 上带有哈希函数的咨询锁 - 存在冲突并且此咨询锁,但该锁锁定所有用户交互 - 所以他不能同时按下两个按钮
INSERT
&SELECT FOR UPDATE
- 很好,但我需要用“待处理”行预填充表,这样它就会用不必要的列污染表模式。- 想要实现: locktable。所以我的意思是创建一个带有类似咨询锁的表。它将有两列
namespace
和id
- 复合主键。每次用户按下按钮#1 时,都会出现INSERT
&FOR UPDATE
表示 namespace=button#1
和 id={userid}
。
我觉得这个(第三)方法有一些隐藏的石头 - 有人知道吗?您能列出这些问题吗?
我确信它在性能方面会比咨询锁差,但是它真的差很多吗?另外,我无法真正找到这种方法的实现 - 看起来我想出了一个愚蠢的想法,但由于某些重要原因没有人使用。
检查 2009 年关于此问题的有趣对话:https://www.postgresql.org/message-id/ [email protected]
INSERT
我的意思是FOR UPDATE
:
INSERT INTO adv_locks VALUES ('btn1', '123') ON CONFLICT ON CONSTRAINT adv_locks_pk DO NOTHING;
SELECT 1 FROM adv_locks WHERE namespace='btn1' and id='123' FOR UPDATE;
这样,表中总有一行可以被锁定。(我还不太关心桌子的大小)
我知道这个顺序可能会被破坏(我的意思是 request#1 创建一行,然后 request#2 更快地选择该行),但这对我来说不是问题。
通过特殊表中的常规行锁来同步应用程序线程的想法是一个糟糕的想法。原因是,只要您想保持锁定,就需要保持数据库事务打开,考虑到我们正在讨论用户交互,这可能需要很长时间。长时间运行的事务是一件坏事,因为它们会阻止重要的清理工作的进展
VACUUM
,并且您最终会遇到臃肿的表以及事务 ID 环绕问题。如果您使用咨询锁(不需要您保持事务打开),那么您的锁表可以避免该问题。但是咨询锁与数据库会话相关联,并且应用程序线程在等待用户完成按下按钮时需要保留数据库会话。这将导致连接池出现问题,如果您希望应用程序性能良好,则需要事务模式下的连接池。
如果锁定的时间跨度只有按一下按钮那么短,您可能会认为我的担忧是荒谬的。确实,这通常可能是很短的时间,但您必须设想一些问题,例如应用程序崩溃或按下按钮和释放按钮之间的网络问题。
我会采用一种解决方案,每当按下和释放按钮时,都会在表中插入或更新一行。如果您使用时间戳,您还可以使用它来使“锁定”超时。如果你使用热更新,那就可以很好地工作。