PostgreSQL:基于多列唯一约束的自动增量

l0b*_*0b0 15 postgresql auto-increment

我的一个表有以下定义:

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)
);
Run Code Online (Sandbox Code Playgroud)

你会如何去递增number列中为每次report,yearmonth 独立?我想避免创建用于每个序列或表(report,year,month)集.

如果PostgreSQL支持 MySQL的MyISAM表中" 在多列索引中的辅助列上 "递增,那将是很好的,但我在手册中找不到这样的功能.

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

另请注意,我正在单独插入事件,因此我不能像其他地方一样使用generate_series.

Den*_*rdy 13

如果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;
Run Code Online (Sandbox Code Playgroud)

这会产生:

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

有一点需要注意.咨询锁保持到释放或会话到期为止.如果在事务期间发生错误,则会保留锁定,您需要手动释放它.

SELECT pg_advisory_unlock('animals'::regclass::int, i)
FROM generate_series(1, array_length(enum_range(NULL::animal_grp),1)) i;
Run Code Online (Sandbox Code Playgroud)

在Postgres 9.1中,您可以丢弃解锁触发器,并用pg_advisory_xact_lock()替换pg_advisory_lock()调用.该交易自动持有,直到交易结束时解除.


另外,我会坚持使用一个好的旧序列.这会让事情变得更快 - 即使看到数据时看起来并不漂亮.

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