如何在PostgreSQL中有效地检查序列中已使用和未使用的值
在PostgreSQL(9.3)中,我有一个表定义为:如何在PostgreSQL中有效地检查序列中已使用和未使用的值,postgresql,sequence,gaps-and-islands,Postgresql,Sequence,Gaps And Islands,在PostgreSQL(9.3)中,我有一个表定义为: CREATE TABLE charts ( recid serial NOT NULL, groupid text NOT NULL, chart_number integer NOT NULL, "timestamp" timestamp without time zone NOT NULL DEFAULT now(), modified timestamp without time zone NOT NULL DEFAU
CREATE TABLE charts
( recid serial NOT NULL,
groupid text NOT NULL,
chart_number integer NOT NULL,
"timestamp" timestamp without time zone NOT NULL DEFAULT now(),
modified timestamp without time zone NOT NULL DEFAULT now(),
donotsee boolean,
CONSTRAINT pk_charts PRIMARY KEY (recid),
CONSTRAINT chart_groupid UNIQUE (groupid),
CONSTRAINT charts_ichart_key UNIQUE (chart_number)
);
CREATE TRIGGER update_modified
BEFORE UPDATE ON charts
FOR EACH ROW EXECUTE PROCEDURE update_modified();
我想用如下顺序替换图表编号:
CREATE SEQUENCE charts_chartnumber_seq START 16047;
因此,通过触发器或函数,添加新的图表记录会自动按升序生成新的图表编号。但是,任何现有的图表记录都不能更改其图表编号,多年来,在指定的图表编号中出现了跳过。因此,在将新图表编号分配给新图表记录之前,我需要确保尚未使用“新”图表编号,并且没有为任何具有图表编号的图表记录分配其他编号
如何做到这一点?在PostgreSQL中,序列确保了您提到的两个要求,即:
但同样,只有当你真的知道自己在做什么时,才应该尝试这样做!人们不自己做序列是有原因的。有一些很难解决的情况(例如并发插入),而且很可能是您过度设计了您的问题案例,这可能可以用一种更好/更干净的方式来解决。序列号通常没有意义,那么为什么要担心呢?但是如果你真的想这样做,那就按照下面这个繁琐的程序去做。请注意,它不是有效的;唯一有效的选择是忘记孔并使用序列 为了避免每次插入时都必须扫描
图表
表格,您应该扫描表格一次,并将未使用的图表编号
值存储在单独的表格中:
CREATE TABLE charts_unused_chart_number AS
SELECT seq.unused
FROM (SELECT max(chart_number) FROM charts) mx,
generate_series(1, mx(max)) seq(unused)
LEFT JOIN charts ON charts.chart_number = seq.unused
WHERE charts.recid IS NULL;
上面的查询生成一个从1到当前最大图表编号
值的连续数字序列,然后左连接图表
表,并查找没有相应图表
数据的记录,这意味着该序列的值作为图表编号
未使用
接下来,创建一个触发器,该触发器在图表
表上的插入
上触发。在触发器函数中,从上述步骤中创建的表中选择一个值:
CREATE FUNCTION pick_unused_chart_number() RETURNS trigger AS $$
BEGIN
-- Get an unused chart number
SELECT unused INTO NEW.chart_number FROM charts_unused_chart_number LIMIT 1;
-- If the table is empty, get one from the sequence
IF NOT FOUND THEN
NEW.chart_number := next_val(charts_chartnumber_seq);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER tr_charts_cn
BEFORE INSERT ON charts
FOR EACH ROW EXECUTE PROCEDURE pick_unused_chart_number();
简单。但是,INSERT
可能会因为某些其他触发器中止该过程或任何其他原因而失败。因此,您需要进行检查以确定是否确实插入了图表编号
:
CREATE FUNCTION verify_chart_number() RETURNS trigger AS $$
BEGIN
-- If you get here, the INSERT was successful, so delete the chart_number
-- from the temporary table.
DELETE FROM charts_unused_chart_number WHERE unused = NEW.chart_number;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER tr_charts_verify
AFTER INSERT ON charts
FOR EACH ROW EXECUTE PROCEDURE verify_chart_number();
在某一点上,具有未使用图表编号的表格将为空,因此,您可以(1)更改表格图表
,以使用序列而不是整数
,用于图表编号
;(2) 删除这两个触发器;(3)未使用图表编号的表格;所有这些都在一个事务中。考虑不要这样做。首先阅读以下相关答案:
如果您仍然坚持填补空白,以下是一个相当有效的解决方案:
1.为避免在表格的大部分中搜索下一个缺少的图表编号
,请创建一个包含所有当前间隙的辅助表:
CREATE TABLE chart_gap AS
SELECT chart_number
FROM generate_series(1, (SELECT max(chart_number) - 1 -- max is no gap
FROM charts)) chart_number
LEFT JOIN charts c USING (chart_number)
WHERE c.chart_number IS NULL;
2.将图表编号
设置为当前最大值,并将图表编号
转换为实际的序列
列:
SELECT setval('charts_chartnumber_seq', max(chart_number)) FROM charts;
ALTER TABLE charts
ALTER COLUMN chart_number SET NOT NULL
, ALTER COLUMN chart_number SET DEFAULT nextval('charts_chartnumber_seq');
ALTER SEQUENCE charts_chartnumber_seq OWNED BY charts.chart_number;
详情:
3.当chart\u gap
不为空时,从那里获取下一个chart\u编号
。
要解决并发事务可能出现的争用条件,而不使事务等待,请使用建议锁:
WITH sel AS (
SELECT chart_number, ... -- other input values
FROM chart_gap
WHERE pg_try_advisory_xact_lock(chart_number)
LIMIT 1
FOR UPDATE
)
, ins AS (
INSERT INTO charts (chart_number, ...) -- other target columns
TABLE sel
RETURNING chart_number
)
DELETE FROM chart_gap c
USING ins i
WHERE i.chart_number = c.chart_number;
或者,Postgres9.5或更高版本具有方便的更新跳过锁定功能
,以使更新更简单、更快:
...
SELECT chart_number, ... -- other input values
FROM chart_gap
LIMIT 1
FOR UPDATE SKIP LOCKED
...
详细说明:
检查结果。填充所有行后,将返回0个受影响的行。(如果找不到,您可以使用签入plpgsql…
)。然后切换到简单的插入:
INSERT INTO charts (...) -- don't list chart_number
VALUES (...); -- don't provide chart_number
虽然您想要的是可能的,但不能仅使用序列来完成,它需要表上的独占锁或重试循环才能工作
您需要:
以独占模式锁定表格
- 通过查询
max
ID,然后在generate_series
上执行left join
查找第一个自由项,找到第一个自由项。如果有的话
- 如果有免费条目,请将其插入
- 如果没有免费输入,请调用nextval
并返回结果
性能将非常糟糕,事务将被序列化。不会有并发。另外,除非运行的第一件事是锁
会影响该表,否则您将面临导致事务中止的死锁
您可以在删除后使用,使其不那么糟糕。。对于每一行
触发器,它跟踪您删除的条目,方法是插入
将它们放入一个单列表中,以跟踪备用ID。然后,您可以从列的默认上的ID分配函数中的表中选择最低ID,从而避免需要显式的表锁、生成系列上的左连接和最大ca