【问题标题】:PostgreSQL: Auto-increment based on multi-column unique constraintPostgreSQL:基于多列唯一约束的自增
【发布时间】:2011-09-16 04:24:43
【问题描述】:

我的一张表具有以下定义:

CREATE TABLE incidents
(
  id serial NOT NULL,
  report integer NOT NULL,
  year integer NOT NULL,
  month integer NOT NULL,
  number integer NOT NULL, -- Report serial number for this period
  ...
  CONSTRAINT PRIMARY KEY (id),
  CONSTRAINT UNIQUE (report, year, month, number)
);

您将如何为每个reportyearmonth 增加number独立?我想避免为每个(reportyearmonth)集创建一个序列或表。

如果 PostgreSQL 支持像 MySQL 的 MyISAM 表一样增加“on a secondary column in a multiple-column index”就好了,但我在manual 中找不到这样的功能。

一个明显的解决方案是选择表中的当前值 + 1,但这对于并发会话显然是不安全的。也许预插入触发器会起作用,但它们是否保证是非并发的?

另外请注意,我是单独插入事件,所以我不能像建议的elsewhere 那样使用generate_series

【问题讨论】:

    标签: postgresql auto-increment


    【解决方案1】:

    我想我找到了更好的解决方案。它不依赖于 grp 类型(可以是枚举、整数和字符串),可以在很多情况下使用。

    myFunc() - 触发器函数。你可以随意命名。 number - 为 grp 的每个存在值增长的自动增量列。 grp - 您要计算的列数。 myTrigger - 为您的表触发。 myTable - 要在其中触发的表。 unique_grp_number_key - 唯一约束键。我们需要使其具有唯一的一对值:grp 和 number。

    ALTER TABLE "myTable"
        ADD CONSTRAINT "unique_grp_number_key" UNIQUE(grp, number);
    
    CREATE OR REPLACE FUNCTION myFunc() RETURNS trigger AS $body_start$
    BEGIN
        SELECT COALESCE(MAX(number) + 1, 1)
            INTO NEW.number
            FROM "myTable"
            WHERE grp = NEW.grp;
        RETURN NEW;
    END;
    $body_start$ LANGUAGE plpgsql;
    
    CREATE TRIGGER myTrigger BEFORE INSERT ON "myTable"
        FOR EACH ROW
        WHEN (NEW.number IS NULL) 
        EXECUTE PROCEDURE myFunc();
    

    它是如何工作的?当您在 myTable 中插入内容时,触发器会调用并检查数字字段是否为空。如果为空,myFunc() 选择 MAX 值,其中 grp 等于您要插入的新 grp 值。它像 auto_increment 一样返回 max value + 1 并将 null number 字段替换为新的 autoincrement 值。

    这个解决方案比 Denis de Bernardy 更独特,因为它不依赖于 grp 类型,但多亏了他,他的代码帮助我编写了我的解决方案。 也许写答案为时已晚,但我在stackoverflow中找不到这个问题的独特解决方案,所以它可以帮助别人。享受并感谢您的帮助!

    【讨论】:

      【解决方案2】:

      我认为这会有所帮助: http://www.varlena.com/GeneralBits/130.php

      请注意,在 MySQL 中,它仅适用于 MyISAM 表。

      PP 我已经测试了咨询锁,发现它们在同一时间内对超过 1 个事务无用。我正在使用 2 个 pgAdmin 窗口。首先是尽可能简单:

      BEGIN;
      INSERT INTO animals (grp,name) VALUES ('mammal','dog');
      COMMIT;
      
      BEGIN;
      INSERT INTO animals (grp,name) VALUES ('mammal','cat');
      COMMIT;
      
      ERROR: duplicate key violates unique constraint "animals_pkey"
      

      第二:

      BEGIN;
      INSERT INTO animals (grp,name) VALUES ('mammal','dog');
      INSERT INTO animals (grp,name) VALUES ('mammal','cat');
      COMMIT;
      
      ERROR: deadlock detected
      SQL state: 40P01
      Detail: Process 3764 waits for ExclusiveLock on advisory lock [46462,46496,2,2]; blocked by process 2712.
      Process 2712 waits for ShareLock on transaction 136759; blocked by process 3764.
      Context: SQL statement "SELECT  pg_advisory_lock( $1 ,  $2 )"
      PL/pgSQL function "animals_id_auto" line 15 at perform
      

      并且数据库被锁定并且无法解锁 - 不知道要解锁什么。

      【讨论】:

      • +1 不错的参考,类似于Denis' answer,但比Denis' answer 更简单。
      • 其实有一个问题:“我想避免为每个(report, year, month)集合创建一个...表。”使用这种方法,我需要一个包含每个 (report, year, month) 集的表,以及 number 的计数器:/ 是否有已知的并发安全的 SELECT MAX(...) 方法?
      • 不幸的是你必须创建表。或者您可以以 MySql 方式锁定表“事件”,但它会很慢。我必须测试咨询 loks 是否可以工作,我不确定。
      • 我正在专门寻找 PostgreSQL 解决方案
      【解决方案3】:

      如果 PostgreSQL 支持像 MySQL 的 MyISAM 表一样支持“在多列索引中的辅助列上”递增,那就太好了

      是的,但请注意,这样做时,MyISAM 会锁定您的整个表。这样就可以安全地找到最大的 +1 而不必担心并发事务。

      在 Postgres 中,您也可以这样做,而且无需锁定整个表。建议锁和触发器就足够了:

      CREATE TYPE animal_grp AS ENUM ('fish','mammal','bird');
      
      CREATE TABLE animals (
          grp animal_grp NOT NULL,
          id INT NOT NULL DEFAULT 0,
          name varchar NOT NULL,
          PRIMARY KEY (grp,id)
      );
      
      CREATE OR REPLACE FUNCTION animals_id_auto()
          RETURNS trigger AS $$
      DECLARE
          _rel_id constant int := 'animals'::regclass::int;
          _grp_id int;
      BEGIN
          _grp_id = array_length(enum_range(NULL, NEW.grp), 1);
      
          -- Obtain an advisory lock on this table/group.
          PERFORM pg_advisory_lock(_rel_id, _grp_id);
      
          SELECT  COALESCE(MAX(id) + 1, 1)
          INTO    NEW.id
          FROM    animals
          WHERE   grp = NEW.grp;
      
          RETURN NEW;
      END;
      $$ LANGUAGE plpgsql STRICT;
      
      CREATE TRIGGER animals_id_auto
          BEFORE INSERT ON animals
          FOR EACH ROW WHEN (NEW.id = 0)
          EXECUTE PROCEDURE animals_id_auto();
      
      CREATE OR REPLACE FUNCTION animals_id_auto_unlock()
          RETURNS trigger AS $$
      DECLARE
          _rel_id constant int := 'animals'::regclass::int;
          _grp_id int;
      BEGIN
          _grp_id = array_length(enum_range(NULL, NEW.grp), 1);
      
          -- Release the lock.
          PERFORM pg_advisory_unlock(_rel_id, _grp_id);
      
          RETURN NEW;
      END;
      $$ LANGUAGE plpgsql STRICT;
      
      CREATE TRIGGER animals_id_auto_unlock
          AFTER INSERT ON animals
          FOR EACH ROW
          EXECUTE PROCEDURE animals_id_auto_unlock();
      
      INSERT INTO animals (grp,name) VALUES
          ('mammal','dog'),('mammal','cat'),
          ('bird','penguin'),('fish','lax'),('mammal','whale'),
          ('bird','ostrich');
      
      SELECT * FROM animals ORDER BY grp,id;
      

      这会产生:

        grp   | id |  name   
      --------+----+---------
       fish   |  1 | lax
       mammal |  1 | dog
       mammal |  2 | cat
       mammal |  3 | whale
       bird   |  1 | penguin
       bird   |  2 | ostrich
      (6 rows)
      

      有一个警告。建议锁一直保持到释放或会话到期。如果在事务过程中发生错误,锁会一直保留,您需要手动释放它。

      SELECT pg_advisory_unlock('animals'::regclass::int, i)
      FROM generate_series(1, array_length(enum_range(NULL::animal_grp),1)) i;
      

      在 Postgres 9.1 中,您可以丢弃解锁触发器,并将 pg_advisory_lock() 调用替换为 pg_advisory_xact_lock()。那个会自动保留到交易结束时才释放。


      另外,我会坚持使用良好的旧序列。这将使事情变得更快——即使当您查看数据时它看起来不那么漂亮。

      最后,还可以通过添加一个额外的表来获得每个(年,月)组合的唯一序列,该表的主键是序列,并且其(年,月)值对其具有唯一约束。

      【讨论】:

      • 这是在可序列化隔离中测试的吗?
      • jordani's answer 使用更简单的代码。您知道代码中额外复杂性的原因吗?
      • @i0b0:在他的示例中,emp_pk_next() 不是并发安全的。
      • @Jordani:是的,咨询锁确保它可以在可序列化的隔离中工作。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-21
      • 1970-01-01
      • 2012-08-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多