Postgres'如果不存在'失败,因为序列存在

时间:2016-09-09 00:49:12

标签: postgresql ddl sequences postgresql-9.5

我正在构建的应用程序中有几个计数器,因为我试图让应用程序根据需要动态创建它们。

举一个简单的例子,如果有人在脚本中输入一个单词,它应该返回之前输入单词的次数。以下是在键入单词example时可以执行的sql示例。

CREATE SEQUENCE IF NOT EXISTS example START WITH 1;
SELECT nextval('example')

这将在第一次运行时返回1,第二次返回2,等等。

问题是当2个人同时点击按钮时。 首先,请注意我的应用程序中发生的事情远不止这些声明,因此重叠的可能性比发生这种情况时更重要。

1> BEGIN;
2> BEGIN;
1> CREATE SEQUENCE IF NOT EXISTS example START WITH 1;
2> CREATE SEQUENCE IF NOT EXISTS example START WITH 1; -- is blocked by previous statement
1> SELECT nextval('example')  -- returns 1 to user.
1> COMMIT;  -- unblocks second connection
2> ERROR:  duplicate key value violates unique constraint 
   "pg_type_typname_nsp_index"
   DETAIL:  Key (typname, typnamespace)=(example, 109649) already exists. 

我的印象是,通过使用“IF NOT EXISTS”,声明应该只是一个无操作,如果它确实存在,但它似乎有这种竞争条件,而事实并非如此。我说竞争条件,因为如果这两个没有同时执行,它就像人们期望的那样工作。

我注意到IF NOT EXISTS对于postgres是相当新的,所以也许他们还没有解决所有的问题呢?

编辑: 我们考虑以这种方式做事的主要原因是为了避免过度锁定。想法是,如果两个人同时增加,使用序列将意味着用户不应该等待另一个(除了,例如,在该示例中,用于该序列的初始创建)

2 个答案:

答案 0 :(得分:4)

序列是数据库模式的一部分。如果您发现自己根据存储在数据库中的数据动态修改架构,那么您可能做错了什么。对于具有特殊性质的序列尤其如此。关于他们在交易方面的行为。具体来说,如果在事务中间递增序列(在The expression contains invalid date constant '#_ofE_Employees, 'Sytem.String') LIKE '1')OR的帮助下)然后回滚该事务,则不会回滚序列的值。所以很有可能,这种行为是您不想要的数据行为。在您的示例中,假设用户尝试添加单词。这导致相应的序列递增。现在想象一下,事务没有完成(例如计算机崩溃)并且它被回滚。您最终会将这个词添加到数据库中,但序列会增加。

对于您提到的特定示例,有一个简单的解决方案;创建一个普通的表来存储所有"序列"。这样的事情会这样做:

nextval

现在我知道这只是一个例子,但如果这种方法对您的实际用例不起作用,请告诉我们,我们可以根据您的需要进行调整。

修改:以下是上述解决方案的工作原理。如果添加了新单词,请运行以下查询(" UPSERT"仅在postgres 9.5+中使用语法):

CREATE TABLE word_frequency (
    word text NOT NULL UNIQUE,
    frequency integer NOT NULL
);

此查询将在频率为1的INSERT INTO word_frequency(word,frequency) VALUES ('foo',1) ON CONFLICT (word) DO UPDATE SET frequency = word_frequency.frequency + excluded.frequency RETURNING frequency; 中插入一个新单词,或者如果单词已存在,则会将现有频率增加1.现在如果两个事务同时尝试执行此操作会发生什么?请考虑以下情形:

word_frequency

一旦客户端2尝试增加foo的频率(用上面的箭头标记),将会发生阻塞,因为该行被另一个事务修改。当客户端1提交时,客户端2将被取消阻止并继续而没有任何错误。这正是我们希望它发挥作用的方式。另请注意,postgresql将使用行级锁定来实现此行为,因此不会阻止其他插入。

答案 1 :(得分:2)

  编辑:我们考虑以这种方式做事的主要原因是   避免过度锁定。想到如果有两个人去   同时增加,使用序列意味着两者都没有   用户应该等待另一个(除了在这个例子中,   用于初始创建该序列)

听起来你正在针对可能不存在的问题进行优化。当然,如果你有100,000个并发用户只插入行(因为序列只会正常使用),可能会对序列产生一些争用,但实际上会有其他瓶颈长在序列阻碍之前。

我建议你首先证明序列是一个问题。通过适当的数据库设计(动态DDL不是),序列将不再是瓶颈。

作为参考,DDL在大多数数据库中不是事务安全的。