Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在PostgreSQL中有效地检查序列中已使用和未使用的值_Postgresql_Sequence_Gaps And Islands - Fatal编程技术网

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

如何在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

在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();
我想用如下顺序替换图表编号:

CREATE SEQUENCE charts_chartnumber_seq START 16047;
因此,通过触发器或函数,添加新的图表记录会自动按升序生成新的图表编号。但是,任何现有的图表记录都不能更改其图表编号,多年来,在指定的图表编号中出现了跳过。因此,在将新图表编号分配给新图表记录之前,我需要确保尚未使用“新”图表编号,并且没有为任何具有图表编号的图表记录分配其他编号


如何做到这一点?

在PostgreSQL中,序列确保了您提到的两个要求,即:

  • 不重复
  • 一旦分配,就不会有任何更改
  • 但由于序列是如何工作的(参见),它不能确保没有跳过。其中,首先想到的两个原因是:

  • 序列如何处理带有插入的并发块(您还可以补充,缓存的概念也使得这不可能)
  • 此外,用户触发的删除是一个无法控制的方面,序列本身无法处理
  • 在这两种情况下,如果您仍然不希望跳过(并且如果您真的知道自己在做什么),您应该有一个单独的结构来分配ID(而不是使用序列)。基本上是一个系统,它有一个“可分配”ID列表存储在一个表中,该表具有以FIFO方式弹出ID的功能。这应该允许您控制删除等


    但同样,只有当你真的知道自己在做什么时,才应该尝试这样做!人们不自己做序列是有原因的。有一些很难解决的情况(例如并发插入),而且很可能是您过度设计了您的问题案例,这可能可以用一种更好/更干净的方式来解决。

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

    为了避免每次插入时都必须扫描
    图表
    表格,您应该扫描表格一次,并将未使用的
    图表编号
    值存储在单独的表格中:

    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