Postgres 'if not exists' 失败,因为序列存在
Postgres 'if not exists' fails because the sequence exists
我正在构建的应用程序中有多个计数器,因为我正在尝试让应用程序根据需要动态创建它们。
举一个简单的例子,如果有人在脚本中输入一个词,它应该 return 该词之前输入的次数。这是一个 sql 的例子,如果他们输入单词 example 就可以执行。
CREATE SEQUENCE IF NOT EXISTS example START WITH 1;
SELECT nextval('example')
第一次return1
,第二次运行,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 来说是相当新的,所以也许他们还没有解决所有问题?
编辑:
我们考虑以这种方式做事的主要原因是为了避免过度锁定。想法是,如果两个人同时递增,使用一个序列将意味着两个用户都不必等待另一个(除了,在这个例子中,为该序列的初始创建)
序列是数据库模式的一部分。如果您发现自己根据存储在数据库中的数据动态修改模式,则您可能做错了什么。对于具有特殊属性的序列尤其如此,例如关于他们在交易方面的行为。具体来说,如果您在事务中间增加一个序列(在 nextval
的帮助下)然后回滚该事务,则序列的值将不会回滚。所以很可能,这种行为是您不希望数据出现的。在您的示例中,假设用户尝试添加单词。这导致相应的序列递增。现在想象一下,由于某种原因(例如,可能是计算机崩溃)事务没有完成并且它被回滚了。你最终会得到这个词没有被添加到数据库但是序列被递增。
对于您提到的特定示例,有一个简单的解决方案;创建一个普通的 table 来存储所有 "sequences"。像那样的东西就可以了:
CREATE TABLE word_frequency (
word text NOT NULL UNIQUE,
frequency integer NOT NULL
);
现在我明白这只是一个示例,但如果此方法不适用于您的实际用例,请告诉我们,我们可以根据您的需要进行调整。
编辑:以上解决方案的工作原理如下。如果添加了一个新词,运行 以下查询("UPSERT" 语法仅适用于 postgres 9.5+):
INSERT INTO word_frequency(word,frequency)
VALUES ('foo',1)
ON CONFLICT (word)
DO UPDATE
SET frequency = word_frequency.frequency + excluded.frequency
RETURNING frequency;
此查询将在 word_frequency
中插入一个频率为 1 的新词,或者如果该词已经存在,它将现有频率增加 1。现在如果两个事务尝试同时执行此操作会发生什么时间?考虑以下场景:
client 1 client 2
-------- --------
BEGIN
BEGIN
UPSERT ('foo',1)
UPSERT ('foo',1) <====
COMMIT
COMMIT
一旦客户端 2 尝试增加 foo 的频率(用上面的箭头标记),该操作将被阻止,因为该行已被不同的事务修改。当客户端 1 提交时,客户端 2 将解除阻塞并继续而不会出现任何错误。这正是我们希望它工作的方式。另请注意,postgresql 将使用行级锁定来实现此行为,因此不会阻止其他插入。
EDIT: The main reason we were considering doing things this way was to
avoid excess locking. The thought being that if two people were to
increment at the same time, using a sequence would mean that neither
user should have to wait for the other (except, as in this example,
for the initial creation of that sequence)
听起来您正在针对一个可能不存在的问题进行优化。当然,如果您有 100,000 个并发用户 只插入行 (因为一个序列只会在正常情况下使用)可能会与序列发生争用,但实际上会有其他争用在序列阻碍之前很久就出现了瓶颈。
我建议你先证明顺序是个问题。通过适当的数据库设计(动态 DDL 不是),序列将不会成为瓶颈。
作为参考,DDL 在大多数数据库中都不是事务安全的。
我正在构建的应用程序中有多个计数器,因为我正在尝试让应用程序根据需要动态创建它们。
举一个简单的例子,如果有人在脚本中输入一个词,它应该 return 该词之前输入的次数。这是一个 sql 的例子,如果他们输入单词 example 就可以执行。
CREATE SEQUENCE IF NOT EXISTS example START WITH 1;
SELECT nextval('example')
第一次return1
,第二次运行,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 来说是相当新的,所以也许他们还没有解决所有问题?
编辑: 我们考虑以这种方式做事的主要原因是为了避免过度锁定。想法是,如果两个人同时递增,使用一个序列将意味着两个用户都不必等待另一个(除了,在这个例子中,为该序列的初始创建)
序列是数据库模式的一部分。如果您发现自己根据存储在数据库中的数据动态修改模式,则您可能做错了什么。对于具有特殊属性的序列尤其如此,例如关于他们在交易方面的行为。具体来说,如果您在事务中间增加一个序列(在 nextval
的帮助下)然后回滚该事务,则序列的值将不会回滚。所以很可能,这种行为是您不希望数据出现的。在您的示例中,假设用户尝试添加单词。这导致相应的序列递增。现在想象一下,由于某种原因(例如,可能是计算机崩溃)事务没有完成并且它被回滚了。你最终会得到这个词没有被添加到数据库但是序列被递增。
对于您提到的特定示例,有一个简单的解决方案;创建一个普通的 table 来存储所有 "sequences"。像那样的东西就可以了:
CREATE TABLE word_frequency (
word text NOT NULL UNIQUE,
frequency integer NOT NULL
);
现在我明白这只是一个示例,但如果此方法不适用于您的实际用例,请告诉我们,我们可以根据您的需要进行调整。
编辑:以上解决方案的工作原理如下。如果添加了一个新词,运行 以下查询("UPSERT" 语法仅适用于 postgres 9.5+):
INSERT INTO word_frequency(word,frequency)
VALUES ('foo',1)
ON CONFLICT (word)
DO UPDATE
SET frequency = word_frequency.frequency + excluded.frequency
RETURNING frequency;
此查询将在 word_frequency
中插入一个频率为 1 的新词,或者如果该词已经存在,它将现有频率增加 1。现在如果两个事务尝试同时执行此操作会发生什么时间?考虑以下场景:
client 1 client 2
-------- --------
BEGIN
BEGIN
UPSERT ('foo',1)
UPSERT ('foo',1) <====
COMMIT
COMMIT
一旦客户端 2 尝试增加 foo 的频率(用上面的箭头标记),该操作将被阻止,因为该行已被不同的事务修改。当客户端 1 提交时,客户端 2 将解除阻塞并继续而不会出现任何错误。这正是我们希望它工作的方式。另请注意,postgresql 将使用行级锁定来实现此行为,因此不会阻止其他插入。
EDIT: The main reason we were considering doing things this way was to avoid excess locking. The thought being that if two people were to increment at the same time, using a sequence would mean that neither user should have to wait for the other (except, as in this example, for the initial creation of that sequence)
听起来您正在针对一个可能不存在的问题进行优化。当然,如果您有 100,000 个并发用户 只插入行 (因为一个序列只会在正常情况下使用)可能会与序列发生争用,但实际上会有其他争用在序列阻碍之前很久就出现了瓶颈。
我建议你先证明顺序是个问题。通过适当的数据库设计(动态 DDL 不是),序列将不会成为瓶颈。
作为参考,DDL 在大多数数据库中都不是事务安全的。