Последователностите имат пропуски, за да позволят едновременни вмъквания. Опитът за избягване на пропуски или повторно използване на изтрити идентификатори създава ужасни проблеми с производителността. Вижте често задаваните въпроси за PostgreSQL wiki.
PostgreSQL SEQUENCE
s се използват за разпределяне на идентификатори. Те само се увеличават и са освободени от обичайните правила за връщане на транзакции, за да позволят на множество транзакции да грабнат нови идентификатори по едно и също време. Това означава, че ако транзакция се върне назад, тези идентификатори се „изхвърлят“; няма запазен списък с "безплатни" идентификатори, а само текущия брояч на идентификаторите. Последователностите също обикновено се увеличават, ако базата данни се изключи нечисто.
Синтетичните ключове (ID) са безсмислени така или иначе. Редът им не е значим, единственото им свойство на значимост е уникалността. Не можете смислено да измерите колко "далеч един от друг" са два идентификатора, нито можете смислено да кажете дали единият е по-голям или по-малък от друг. Всичко, което можете да направите, е да кажете „равно“ или „не равно“. Всичко друго е опасно. Не бива да се интересувате от пропуски.
Ако имате нужда от последователност без пропуски, която използва повторно изтрити идентификационни номера, можете да имате такава, просто трябва да се откажете от огромно количество производителност за нея - по-специално, не можете да имате никакъв паралел на INSERT
и изобщо, защото трябва да сканирате таблицата за най-ниския безплатен идентификатор, като заключвате таблицата за запис, така че никоя друга транзакция не може да претендира за същия идентификатор. Опитайте да потърсите „последователност без празнини на postgresql“.
Най-простият подход е да използвате таблица с броячи и функция, която получава следващия идентификатор. Ето една обобщена версия, която използва таблица с броячи за генериране на последователни идентификатори без пропуски; обаче не използва повторно идентификатори.
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';
Употреба:
INSERT INTO dummy(id, blah)
VALUES ( get_next_id('thetable_id_counter','last_id'), 42 );
Имайте предвид, че когато една отворена транзакция получи идентификатор, всички други транзакции, които се опитват да извикат get_next_id
ще блокира, докато 1-вата транзакция не бъде ангажирана или се върне назад. Това е неизбежно и за идентификатори без пропуски и е по проект.
Ако искате да съхранявате множество броячи за различни цели в таблица, просто добавете параметър към горната функция, добавете колона към таблицата с броячи и добавете WHERE
клауза към UPDATE
който съответства на параметъра към добавената колона. По този начин можете да имате множество независимо заключени броячи. Не просто добавете допълнителни колони за нови броячи.
Тази функция не използва повторно изтрити идентификатори, а само избягва въвеждането на пропуски.
За повторно използване на идентификатори съветвам ... да не използвате повторно идентификатори.
Ако наистина трябва, можете да го направите, като добавите ON INSERT OR UPDATE OR DELETE
задействане на таблицата от интерес, която добавя изтрити идентификатори към странична таблица със свободен списък и ги премахва от таблицата със свободен списък, когато са INSERT
изд. Обработете UPDATE
като DELETE
последвано от INSERT
. Сега променете функцията за генериране на ID по-горе, така че да направи SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1
и ако бъде намерен, DELETE
е този ред. IF NOT FOUND
получава нов идентификатор от таблицата на генератора както обикновено. Ето едно непроверено разширение на предишната функция за поддръжка на повторна употреба:
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;