我有一个名为 as 的表'games',其中包含一个名为 as 的列'title',该列是唯一的,数据库中使用PostgreSQL
'game'我有一个用户输入表单,允许他在表中插入新的内容'games'。插入新游戏的函数会检查之前输入的'game'相同游戏是否'title'已经存在,为此,我得到count of rows, 具有相同的游戏'title'。
我为此使用事务,开始时的插入函数使用BEGIN,获取行计数,如果行计数为 0,则插入新行,并且在过程完成后,它会COMMITS发生变化。
问题是,title如果用户同时提交,两个相同的游戏有可能会被插入两次,因为我只是获取要检查重复记录的行数,并且每个事务都会被插入彼此隔离
我想在获取行数时锁定表:
LOCK TABLE games IN ACCESS EXCLUSIVE MODE;
SELECT count(id) FROM games WHERE games.title = 'new_game_title'
Run Code Online (Sandbox Code Playgroud)
这也会锁定表进行读取(这意味着另一个事务必须等待,直到当前事务成功完成)。这将解决问题,这正是我所怀疑的。有没有更好的方法来解决这个问题(避免重复使用games相同的title)
在这种情况下您不需要锁定您的表。
相反,您可以使用以下方法之一:
UNIQUE为确实必须唯一的列定义索引。在这种情况下,第一个事务将成功,第二个事务将出错。AFTER INSERT OR UPDATE OR DELETE将检查您的条件的触发器,如果不成立,则应该RAISE出错,这将中止违规事务在所有这些情况下,您的客户端代码应该准备好正确处理可能因执行语句而返回的失败(例如失败的事务)。
使用最高的事务隔离(可序列化),您可以实现与实际问题类似的效果。但请注意这可能会失败ERROR: could not serialize access due to concurrent update
我不完全同意约束方法。您应该有一个约束来保护数据完整性,但依赖该约束迫使您不仅要识别发生了什么错误,还要识别哪个约束导致了错误。问题并不像一些人讨论的那样捕获错误,而是识别导致错误的原因并提供人类可读的失败原因。根据您的应用程序所使用的语言,这几乎是不可能的。例如:告诉用户“游戏标题 [foo] 已经存在”而不是“游戏必须有价格”作为单独的约束。
有一个语句可以替代两阶段方法:
INSERT INTO games ( [column1], ... )
SELECT [value1], ...
WHERE NOT EXISTS ( SELECT x FROM games as g2 WHERE games.title = g2.title );
Run Code Online (Sandbox Code Playgroud)
我想澄清这一点......这并不是唯一约束(需要额外的索引数据)的替代方案。您必须拥有一个来保护您的数据免遭损坏。