在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');
Run Code Online (Sandbox Code Playgroud)
第二个插入失败但序列groups_id_seq已经递增,所以当添加'bar'时,它会在id号中留下一个间隙.
有没有办法告诉PostgreSQL仅在满足其他约束时才获取下一个值,或者如果名称不重复,我应该首先使用SELECT检查?这仍然不能保证缺乏差距,但至少当有另一个进程试图同时插入相同的名称时,它会减少它们的数量
bor*_*yer 12
我不这么认为:序列的基本特征是可能存在间隙(考虑两个并发事务,其中一个执行ROLLBACK).你应该忽略差距.为什么他们是你的问题?
虽然很麻烦,但这样做是可能的.正如bortzmeyer所说,依赖连续序列的值是危险的,所以如果可以的话,最好留下原样.
如果你不能:
对表的每次访问都可能导致行具有某个名称(即,每个INSERT表都有一个,如果你允许它(虽然这是不好的做法)每一个都UPDATE可以改变name字段)必须在一个锁定的事务中这样做soemthing第一.最简单且性能最差的选项是简单地使用锁定整个表LOCK users IN EXCLUSIVE MODE(添加最后3个单词允许其他进程并发读取访问,这是安全的).
然而,这是一个非常粗略的锁定,如果有许多并发修改,将会降低性能users; 更好的选择是将另一个必须已存在的表中的单个相应行锁定.此行可以锁定SELECT ... FOR UPDATE.只有在处理对另一个"父"表具有FK依赖关系的"子"表时才有意义.
例如,想象暂时我们实际上是在尝试orders为a 安全地创建新的customer,并且这些命令以某种方式识别"名称".(我知道,可怜的例子......) orders有一个FK依赖customers.然后,为了防止为给定客户创建具有相同名称的两个订单,您可以执行以下操作:
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;
Run Code Online (Sandbox Code Playgroud)
请注意,这是不足够简单地锁定相应行中users有SELECT ... FOR UPDATE-如果该行不存在,几个并发进程可以同时报告该行不存在,然后试图同时插入,导致失败的交易,因此序列差距.
锁定方案都可以使用; 重要的是,任何试图创建具有相同名称的行的人都必须尝试锁定同一个对象.