PostgreSQL无间隙序列

fat*_*rog 23 postgresql ruby-on-rails sequence

我正在从MySql迁移到Postgres,我注意到当您从MySql中删除行时,这些行的唯一ID会在您创建新行时重复使用.使用Postgres,如果您创建行并删除它们,则不会再次使用唯一ID.

在Postgres有这种行为的原因吗?在这种情况下,我可以使它更像MySql吗?

Cra*_*ger 49

序列有间隙以允许并发插入.尝试避免间隙或重新使用已删除的ID会产生可怕的性能问题.请参阅PostgreSQL wiki常见问题解答.

PostgreSQL的SEQUENCE小号用于分配的ID.这些只会增加,并且它们不受通常的事务回滚规则的限制,以允许多个事务同时获取新的ID.这意味着如果事务回滚,那些ID将被"丢弃"; 没有保留"免费"ID的列表,只有当前的ID计数器.如果数据库不正常地关闭,序列通常也会增加.

无论如何,合成密钥(ID)毫无意义.他们的命令并不重要,他们唯一的重要属性是独特性.你不能有意义地衡量两个ID是如何"相距甚远"的,也不能有意义地说一个是大于还是小于另一个.你所能做的就是说"平等"或"不平等".其他任何事情都是不安全的.你不应该关心差距.

如果你需要一个重复使用已删除ID的无间隙序列,你可以拥有一个,你只需要放弃它的大量性能 - 特别是你根本不能有任何并发INSERT,因为你必须扫描该表为最低的自由ID,锁定表进行写入,因此没有其他事务可以声明相同的ID.尝试搜索"postgresql无间隙序列".

最简单的方法是使用计数器表和获取下一个ID的函数.这是一个使用计数器表生成连续无间隙ID的通用版本; 但它不会重复使用ID.

CREATE TABLE thetable_id_counter ( last_id integer not null );
INSERT INTO thetable_id_counter VALUES (0);

CREATE OR REPLACE FUNCTION get_next_id(countertable regclass, countercolumn text) RETURNS integer AS $$
DECLARE
    next_value integer;
BEGIN
    EXECUTE format('UPDATE %s SET %I = %I + 1 RETURNING %I', countertable, countercolumn, countercolumn, countercolumn) INTO next_value;
    RETURN next_value;
END;
$$ LANGUAGE plpgsql;

COMMENT ON get_next_id(countername regclass) IS 'Increment and return value from integer column $2 in table $1';
Run Code Online (Sandbox Code Playgroud)

用法:

INSERT INTO dummy(id, blah) 
VALUES ( get_next_id('thetable_id_counter','last_id'), 42 );
Run Code Online (Sandbox Code Playgroud)

请注意,当一个打开的事务获得一个ID时,尝试调用的所有其他事务get_next_id将阻塞,直到第一个事务提交或回滚.这是不可避免的,对于无间隙的ID而言是设计的.

如果要在表中为不同目的存储多个计数器,只需在上面的函数中添加一个参数,向计数器表添加一个列,然后添加一个WHERE子句,UPDATE使其与添加的列的参数匹配.这样,您可以拥有多个独立锁定的计数器行.不要只是添加额外的列新的计数器.

此功能不会重复使用已删除的ID,只是避免引入间隙.

要重新使用ID,我建议...不要重复使用ID.

如果你真的必须这样做,你可以通过ON INSERT OR UPDATE OR DELETE在感兴趣的表上添加一个触发器来执行此操作,该触发器将已删除的ID添加到空闲列表边表,并在INSERT编辑时将它们从空闲列表中删除.对待UPDATEa DELETE后跟a INSERT.现在修改上面的ID生成函数,以便它执行a SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1,如果找到,则执行DELETE该行.IF NOT FOUND正常从生成器表中获取新ID.这是一个未经测试的先前函数扩展,以支持重用:

CREATE OR REPLACE FUNCTION get_next_id_reuse(countertable regclass, countercolumn text, freelisttable regclass, freelistcolumn text) RETURNS integer AS $$
DECLARE
    next_value integer;
BEGIN
    EXECUTE format('SELECT %I FROM %s FOR UPDATE LIMIT 1', freelistcolumn, freelisttable) INTO next_value;
    IF next_value IS NOT NULL THEN
        EXECUTE format('DELETE FROM %s WHERE %I = %L', freelisttable, freelistcolumn, next_value);
    ELSE
        EXECUTE format('UPDATE %s SET %I = %I + 1 RETURNING %I', countertable, countercolumn, countercolumn, countercolumn) INTO next_value;
    END IF;
    RETURN next_value;
END;
$$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)