有没有办法避免在PostgreSQL中插入失败时调用nextval()?

时间:2010-01-10 09:14:18

标签: sql postgresql

在PostgreSQL数据库中,我有一个包含主键的表和另一个需要唯一的字段。

  CREATE TABLE users (
    id      INTEGER PRIMARY KEY DEFAULT nextval('groups_id_seq'::regclass),
    name    VARCHAR(255) UNIQUE NOT NULL
  );

  INSERT users (name) VALUES ('foo');
  INSERT users (name) VALUES ('foo');
  INSERT users (name) VALUES ('bar');

第二个插入失败但序列groups_id_seq已经递增,所以当添加'bar'时,它会在id号中留下一个间隙。

有没有办法告诉PostgreSQL仅在满足其他约束时才获取下一个值,或者如果名称不重复,我应该首先使用SELECT检查?这仍然不能保证缺乏差距,但至少当有另一个进程试图同时插入相同的名称时,它会减少它们的数量

3 个答案:

答案 0 :(得分:12)

我不这么认为:序列的基本特征是可能存在间隙(考虑两个并发事务,其中一个执行ROLLBACK)。你应该忽略差距。为什么他们是你的问题?

答案 1 :(得分:6)

如果你需要无间隙序列 - 有办法做到这一点,但这不是微不足道的,而且肯定要慢得多。

另外 - 如果你担心“使用太多的ID” - 只需将id定义为bigserial。

答案 2 :(得分:5)

这样做虽然很麻烦,但是很有可能。作为bortzmeyer says,依赖序列连续的值是很危险的,所以如果可以的话,最好只保留原样。

如果你不能:

对表的每次访问都可能导致行具有某个名称(即每个INSERT到该表,并且如果您允许它(虽然这是不好的做法)每个UPDATE那个可以更改name字段)必须在首先锁定soemthing 的事务中执行。最简单且性能最差的选项是使用LOCK users IN EXCLUSIVE MODE简单地锁定整个表(添加最后3个字允许其他进程并发读取访问,这是安全的)。

然而,如果对users进行多次并发修改,这将是一个非常粗略的锁定,会降低性能;更好的选择是将另一个必须已存在的表中的单个相应行锁定。可以使用SELECT ... FOR UPDATE锁定此行。这只有在使用对另一个“父”表具有FK依赖关系的“子”表时才有意义。

例如,想象一下,我们实际上正在尝试为orders安全地创建新customer,并且这些订单以某种方式识别“名称”。 (我知道,可怜的例子......)orderscustomers有一个FK依赖。然后,为了防止为给定客户创建具有相同名称的两个订单,您可以执行以下操作:

BEGIN;

-- Customer 'jbloggs' must exist for this to work.  
SELECT 1 FROM customers
WHERE id = 'jbloggs'
FOR UPDATE

-- Provided every attempt to create an order performs the above step first,
-- at this point, we will have exclusive access to all orders for jbloggs.
SELECT 1 FROM orders
WHERE id = 'jbloggs'
AND order_name = 'foo'

-- Determine if the preceding query returned a row or not.
-- If it did not:
INSERT orders (id, name) VALUES ('jbloggs', 'foo');

-- Regardless, end the transaction:
END;

请注意,足以简单地将users中的相应行锁定为SELECT ... FOR UPDATE - 如果该行尚不存在,则可能会同时报告多个并发进程该行不存在,然后尝试同时插入,导致事务失败,从而导致序列间隙。

任何一种锁定方案都可以使用;重要的是任何试图创建具有相同名称的行的人都必须尝试锁定同一个对象