如何有效地检查PostgreSQL中已使用和未使用的值的序列

时间:2015-09-20 21:14:20

标签: 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 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();

我想用以下序列替换chart_number:

CREATE SEQUENCE charts_chartnumber_seq START 16047;

因此,通过触发器或功能,添加新的图表记录会自动按升序生成新的图表编号。但是,现有的图表记录不能更改其图表编号,并且多年来已在指定的图表编号中跳过。因此,在为新的图表记录分配新的图表编号之前,我需要确保" new"图表编号尚未使用,任何带图表编号的图表记录都没有分配不同的编号。

如何做到这一点?

3 个答案:

答案 0 :(得分:4)

考虑这样做。首先阅读这些相关答案:

如果你仍然坚持填补空白,这是一个非常有效的解决方案:

1。为了避免在下一个缺失的chart_number中搜索表的大部分内容,请创建一个包含所有当前空白 一次的辅助表 < /强>:

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。charts_chartnumber_seq设置为当前最大值,并将chart_number转换为实际 serial 列:

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_gap不为空,但从那里获取下一个chart_number。 要解决带有并发事务的 可能的竞争条件 ,而不使事务等待,请使用咨询锁:

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;

或者 ,Postgres 9.5 或更高版本有方便的FOR UPDATE SKIP LOCKED使这更简单,更快:

...
   SELECT chart_number, ...  -- other input values
   FROM   chart_gap
   LIMIT  1
   FOR    UPDATE SKIP LOCKED
...

详细说明:

检查结果。填写完所有行后,将返回受影响的0行。 (您可以使用IF NOT FOUND THEN ...检入plpgsql)。然后切换到简单的INSERT

   INSERT INTO charts (...)  -- don't list chart_number
   VALUES (...);  --  don't provide chart_number

答案 1 :(得分:2)

序列号通常没有意义,为什么要担心?但是,如果你真的想要这个,那么按照下面的繁琐程序。请注意,有效;唯一有效的选择是忘记漏洞并使用序列。

为了避免必须扫描每个插入的charts表,您应该扫描一次表并将未使用的chart_number值存储在单独的表中:

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到当前最大chart_number值,然后LEFT JOINcharts表,并查找没有对应的记录charts数据,意味着该系列的值未被用作chart_number

接下来,您将创建一个触发器,该触发器在INSERT表的charts上触发。在触发器功能中,从上面步骤中创建的表中选择一个值:

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可能会失败,因为某些其他触发器会中止该过程或任何其他原因。因此,您需要检查以确定chart_number确实已插入:

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)ALTER TABLE charts使用序列而不是integer作为chart_number; (2)删除两个触发器; (3)未使用图表编号的表格;所有这一切都在一次交易中。

答案 2 :(得分:1)

虽然您想要的是可能的,但只能使用SEQUENCE来完成它,并且它需要对表或重试循环进行独占锁定才能工作。

你需要:

  • LOCK thetable IN EXCLUSIVE MODE
  • 通过查询max ID然后left join超过generate_series查找第一个免费ID来查找第一个免费ID。如果有的话。
  • 如果有空闲条目,请将其插入。
  • 如果没有免费参赛作品,请致电nextval并返回结果。

性能绝对可怕,交易将被序列化。没有并发性。此外,除非LOCK是您运行的第一个影响该表的内容,否则您将面临导致事务中止的死锁。

使用AFTER DELETE .. FOR EACH ROW触发器可以通过INSERT跟踪您删除的条目,将SELECT跟踪到一个可以跟踪备用ID的单列表,从而减少这种情况。然后,您可以defaultleft join上ID分配函数中表中的最低ID,从而避免显式表锁,generate_series max }和SELECT ... FOR UPDATE SKIP LOCKED电话。仍然会在免费ID表的锁上序列化事务。在PostgreSQL中,您甚至可以使用SEQUENCE来解决这个问题。所以如果你在9.5上,你实际上可以做到这一点,虽然它仍然很慢。

强烈建议您直接使用ll_addr,而不必重复使用值。