«INSERT INTO t (x) .. SELECT MAX (tx) + 1 ..» иногда возвращает «DUPLICATE KEY» - PullRequest
1 голос
/ 15 января 2020

Добрый вечер,

У меня проблема с простым "INSERT" sql заявлением, что я предполагал, что это "бомба безопасна", но иногда я сталкиваюсь с ошибкой "duplicate key".

Хорошо, я дам вам простой sql код

- инструкция 1: создание таблицы

CREATE TABLE tab_1 (
    code integer,
    field_1 integer,
    PRIMARY KEY (code)
) WITHOUT OIDS;

- инструкция2: вставка

INSERT INTO tab_1 (code)
SELECT 
    (SELECT COALESCE(MAX(code), 0) + 1 FROM tab_1
    )
;

Ну, проблема, с которой я сталкиваюсь, заключается в том, что «иногда» без какой-либо (ИМХО) причины, инструкция 2 возвращает «дубликат ключа» на вставке для поля "код". Очевидно, что это происходит случайно, и я не могу объяснить, «почему» это происходит или «как», я могу воспроизвести проблему за столом. Единственное решение, которое, кажется, работает, это перехватить ошибку и повторить вставку, пока она не пройдет нормально.

Хорошо, значит, «решение подано», но основная проблема заключается в том, что этот способ использования «вставки» в "широко используется в ОГРОМНОМ приложении с МНОЖЕМ таблиц и переупорядочивает весь код, в котором вызывается одна и та же операция (отклонено для разных таблиц) - это то, чего я хочу избежать.

Спасибо за помощь

Ответы [ 2 ]

2 голосов
/ 16 января 2020

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

Для действительно атомарного c способа сделать то, что вы хотите, либо используйте последовательность (если вы не возражаете против пробелов, это лучший решение) или используйте отдельную таблицу с одной записью, которую вы увеличиваете атомарно с помощью

UPDATE counter_table
SET c = c + 1
RETURNING c;

Другой способ - использовать уровень изоляции транзакций SERIALIZABLE, но это будет не лучше, чем ваш код.

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

2 голосов
/ 15 января 2020

Вместо этого используйте SERIAL , что похоже на AUTOINCREMENT.

CREATE TABLE table_name(
    id SERIAL
);

Никогда не принимайте COUNT + 1, поскольку два пользователя одновременно могут получить одинаковый идентификатор или перепутать ID (если вы используете этот идентификатор далее).

Для хорошего порядка: на языке программирования, таком как java, после вставки (без идентификатора) вы можете использовать getGeneratedKeys для получения сгенерированного первичного ключа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...