PostgreSQL无缝序列

我正在从MySql移动到Postgres,我注意到,当你从MySql中删除行时,这些行的唯一ID将在你重新使用时重新使用。 使用Postgres,如果您创build行并删除它们,则不会再使用唯一标识符。

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

序列有差距允许并发插入。 试图避免差距或重新使用已删除的ID会导致可怕的性能问题。 查看PostgreSQL维基FAQ 。

PostgreSQL SEQUENCE被用来分配ID。 这些只会增加,并且可以免除通常的事务回滚规则,以允许多个事务同时获取新的ID。 这意味着如果一个事务回滚,这些ID被“扔掉”; 没有“免费”ID列表,只有当前的ID柜台。 如果数据库不清洁,序列通常也会增加。

合成密钥(ID)无论如何都是毫无意义的。 他们的顺序并不重要,他们唯一的意义是唯一的。 你不能有意义地衡量两个ID是多么“相距甚远”,你也不能有意义地说出一个是多大还是小于另一个。 你所能做的只是说“相等”或“不相等”。 其他任何事情都是不安全的。 你不应该在乎差距。

如果您需要一个重复使用已删除ID的无间隙序列,您可以拥有一个,您只需要放弃大量的性能 – 尤其是,根本不能在INSERT上实现任何并发性,因为您必须扫描表的最低空闲ID,locking表写入,以便其他事务可以要求相同的ID。 尝试search“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 %I 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'; 

用法:

 INSERT INTO dummy(id, blah) VALUES ( get_next_id('thetable_id_counter','last_id'), 42 ); 

请注意,当一个打开的事务已经获得一个ID时,所有其他试图调用get_next_id将会阻塞,直到第一个事务提交或回滚。 这是不可避免的,对于无缝ID,是由devise。

如果要在表中为不同目的存储多个计数器,只需向上述函数中添加一个参数,向计数器表中添加一列,然后向UPDATE中添加一个WHERE子句,以便将该参数与添加的列相匹配。 这样你可以有多个独立locking的计数器行。 不要只为新的计数器添加额外的列。

这个函数不会重复使用已删除的ID,它只是避免了引入空白。

要重新使用ID我build议…不要重新使用ID。

如果你真的必须这样做,你可以在感兴趣的表上添加一个ON INSERT OR UPDATE OR DELETE触发器,将删除的ID添加到自由列表旁边表中,并在INSERT时将它们从自由列表中删除编辑。 将UPDATE作为DELETE然后是INSERT 。 现在修改上面的ID生成函数,以便它执行SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1 ,如果find,则DELETE那行。 IF NOT FOUND从正常情况下从生成器表中获取一个新的ID。 这是一个未经testing的先前function的扩展,以支持重复使用:

 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 %I FOR UPDATE LIMIT 1', freelisttable, freelistcolumn) INTO next_value; IF next_value IS NOT NULL THEN EXECUTE format('DELETE FROM %I WHERE %I = %L', freelisttable, freelistcolumn, next_value); ELSE EXECUTE format('UPDATE %I SET %I = %I + 1 RETURNING %I', countertable, countercolumn, countercolumn, countercolumn) INTO next_value; END IF; RETURN next_value; END; $$ LANGUAGE 'plpgsql';