Послепроцессорные последовательности PostgreSQL

Я перехожу из MySql в Postgres, и я заметил, что когда вы удаляете строки из MySql, уникальные идентификаторы для этих строк повторно используются, когда вы создаете новые. В Postgres, если вы создаете строки и удаляете их, уникальные идентификаторы не используются снова.

Есть ли причина такого поведения в Postgres? Могу ли я заставить его действовать больше как MySql в этом случае?

Ответ 1

Последовательности имеют пробелы, позволяющие создавать параллельные вставки. Попытка избежать пробелов или повторного использования удаленных идентификаторов создает ужасные проблемы с производительностью. См. Часто задаваемые вопросы по вики PostgreSQL.

PostgreSQL SEQUENCE используются для выделения идентификаторов. Они только увеличиваются, и они освобождаются от обычных правил отката транзакций, позволяя нескольким транзакциям одновременно захватывать новые идентификаторы. Это означает, что если транзакция откатывается назад, эти идентификаторы "отбрасываются"; там нет списка "свободных" идентификаторов, только текущий счетчик ID. Последовательности также обычно увеличиваются, если база данных отключается нечисто.

Синтетические ключи (идентификаторы) в любом случае бессмысленны. Их порядок незначителен, их единственное свойство значимости - это уникальность. Вы не можете осмысленно измерить, как "далеко друг от друга" есть два идентификатора, и вы не можете осмысленно сказать, если он больше или меньше другого. Все, что вы можете сделать, это сказать "равно" или "не равно". Все остальное небезопасно. Вы не должны заботиться о пробелах.

Если вам нужна бесщеточная последовательность, которая повторно использует удаленные идентификаторы, вы можете иметь ее, вам просто нужно отказаться от огромного количества производительности для нее - в частности, вы не можете иметь параллелизм на INSERT вообще, потому что вам нужно сканировать таблицу для минимального бесплатного идентификатора, блокировку таблицы для записи, чтобы никакая другая транзакция не могла требовать того же 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';

Применение:

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

Обратите внимание, что когда одна открытая транзакция получила идентификатор, все остальные транзакции, которые пытаются вызвать get_next_id, блокируются до тех пор, пока первая транзакция не завершится или не вернется. Это неизбежно и для бесщеточных идентификаторов и по дизайну.

Если вы хотите хранить несколько счетчиков для разных целей в таблице, просто добавьте параметр к указанной выше функции, добавьте столбец в таблицу счетчиков и добавьте предложение WHERE в UPDATE которое соответствует параметру для добавленного столбца. Таким образом, вы можете иметь несколько независимых счетчиков строк. Не добавляйте дополнительные столбцы для новых счетчиков.

Эта функция не использует повторно удаленные идентификаторы, она просто избегает введения пробелов.

Для повторного использования идентификаторов я советую... не повторять использование идентификаторов.

Если это действительно так, вы можете сделать это, добавив триггер ON INSERT OR UPDATE OR DELETE в интересующую таблицу, которая добавляет удаленные идентификаторы в таблицу со списком свободных списков и удаляет их из таблицы бесплатных списков, когда они являются INSERT редактор Обращайтесь к UPDATE как DELETE за которым следует INSERT. Теперь измените функцию генерации идентификатора выше, чтобы он сделал 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;