Альтернатива полю Postgres SERIAL для решения возрастающих значений, когда ON CONFLICT вызывает обновление - PullRequest
1 голос
/ 11 марта 2019

Недавно я был замечен тем, что не знал о проблеме увеличения полей SERIAL вне зависимости от того, вставлены данные или нет.

В большинстве ответов, которые я читал по этому вопросу, обсуждается предотвращение появления дыр встолбец, который, я уверен, в большинстве случаев не относится к поставленному вопросу, и, конечно, не в моем случае.

Моя ситуация заключалась в том, что конкретный пользователь моего программного обеспечения былиспользование функции таким образом, чтобы миллионы возвышений выполнялись на одной записи.Эта запись использовалась в качестве информации о состоянии, и по своей наивности я блаженно не знал о предстоящем сбое, когда поля id INTEGER nextval () достигли своего предела, что является следующей ошибкой:

ERROR: целое число вне диапазонаСостояние SQL: 22003

Таким образом, мой вопрос заключается в том, как я могу запретить приращению полей id следующего значения последовательности в случае отката конфликта.

Я жду, что другие добавят своизнание к моему решению.

1 Ответ

0 голосов
/ 11 марта 2019

Моим непосредственным решением этой проблемы, которая облегчила ситуацию вне диапазона, было изменение столбца на BIGINT, как показано ниже:

ALTER TABLE MyTable ALTER COLUMN idMyTable TYPE BIGINT;

Количество записей в моем случае было чрезвычайно маленьким (<1000), так что это было тривиальное изменение для выполнения. </p>

Как только это закончилось, пришло время искать решение основной проблемы. Мое решение вряд ли будет таким же эффективным, как использование поля SERIAL, поэтому имейте это в виду, исходя из вашего варианта использования, если вы собираетесь реализовать что-то похожее на то, что я сделал - всегда где-то есть компромисс.

Рассмотрим следующую таблицу и результирующую вставку данных / запрос:

CREATE TABLE TestTable ( id SERIAL PRIMARY KEY NOT NULL, Key TEXT UNIQUE NOT NULL, Val TEXT );
INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'banana') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'apple') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'peach') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Animal', 'horse') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
SELECT * FROM TestTable;
id Key    Val
1  Fruit  peach
4  Animal horse

В этом случае каждый конфликт во время обновления Fruit повышал значение SERIAL, даже если он не создавал новую запись в TestTable.

Теперь это обходной путь, с которым я сейчас работаю. Если кто-нибудь знает, как объединить имя таблицы в 'NEW.id', я хотел бы услышать это, поскольку мне нравится называть мои столбцы идентификаторов idTablename для согласованности.

CREATE OR REPLACE FUNCTION IncrementSerial()
RETURNS trigger AS $fn$
BEGIN
  EXECUTE format('SELECT COALESCE( MAX( id ), 0 ) + 1 FROM %I.%I;',TG_TABLE_SCHEMA,TG_TABLE_NAME) INTO NEW.id;
  RETURN NEW;
END
$fn$ LANGUAGE 'plpgsql'

CREATE TABLE TestTable ( id INTEGER PRIMARY KEY NOT NULL, Key TEXT UNIQUE NOT NULL, Val TEXT );

CREATE TRIGGER trgIncrementSerial
BEFORE INSERT ON TestTable
  FOR EACH ROW
    EXECUTE PROCEDURE IncrementSerial()

INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'banana') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'apple') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'peach') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Animal', 'horse') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
SELECT * FROM TestTable;
id Key    Val
1  Fruit  peach
2  Animal horse

Как вы можете видеть, SERIAL id теперь просто использует следующий наибольший номер id, что идеально подходит для большинства моих случаев использования.

Очевидно, что это будет проблемой, если ключ id всегда должен быть уникальным, так как удаление последней записи освободит этот идентификатор. Если это не проблема (т. Е. Где идентификатор используется только для ссылок и каскадов), то это может быть хорошим решением для вас.

...