Почему Postgres генерирует уже использованное значение PK?

20

Я использую Django, и время от времени я получаю эту ошибку:

IntegrityError: двойное значение ключа нарушает уникальное ограничение "myapp_mymodel_pkey". ПОДРОБНЕЕ
: Key (id) = (1) уже существует.

Моя база данных Postgres на самом деле имеет объект myapp_mymodel с первичным ключом 1.

Почему Postgres пытается снова использовать этот первичный ключ? Или это, скорее всего, мое приложение (или ORM Джанго) вызывает это?

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

Тот факт, что эта ошибка является настолько неустойчивой (возникала всего 3 или около того раза в 2 недели - никакой другой нагрузки на БД, только я тестирую свое приложение), заставляет меня так опасаться проблемы низкого уровня.

orokusaki
источник
Джанго особо заявляет, что первичный ключ генерируется СУБД, если не указано иное - теперь я не знаю, что делал @orokusaky в его коде на python, но я оказался на этой странице, потому что я совершенно уверен, что у меня нет кода пытаясь использовать определенный первичный ключ, и я никогда не видел, чтобы СУБД пыталась использовать неправильный.
MCCC

Ответы:

34

PostgreSQL не будет пытаться вставлять дублирующиеся значения самостоятельно, это делает вы (ваше приложение, включая ORM).

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

SELECT setval('your_sequence_name', (SELECT max(id) FROM your_table));

Второй означает отладку.

Django (или любой другой популярный фреймворк) не сбрасывает последовательности самостоятельно - в противном случае у нас будут подобные вопросы через день.

Dezso
источник
Стоит ли отмечать (также основываясь на ответе @ andi здесь) о различных уровнях изоляции? Например, если второй запрос поступает до того, как первый завершен, возможно ли, учитывая сценарий, в котором я не использую транзакции, вставить запись, которая приводит к получению max(id)до завершения первого запроса, а затем приводит к тот же результат?
орокусаки
5

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

Рассмотрим следующий столбец в вашей таблице, который является первичным ключом, определенным Django ORM для postgres

id serial NOT NULL

Чье значение по умолчанию установлено

nextval('table_name_id_seq'::regclass)

Последовательность оценивается только тогда, когда поле id установлено как пустое. Но это проблема, если в таблице уже есть записи.

Вопрос в том, почему эти ранние записи не вызывали обновления последовательности? Это связано с тем, что значение id было явно указано для всех предыдущих записей.

В моем случае эти начальные записи были загружены из фикстур через миграции.

Эта проблема также может стать хитрой через пользовательские записи со случайным значением PK.

Скажем, например. В вашей таблице 10 записей. Вы делаете явную запись с PK = 15. Следующие четыре вставки через код будут работать отлично, но пятая вставит исключение.

DETAIL: Key (id)=(15) already exists.
Абхишек
источник
Спасибо тебе за этот пост. Я долго отлаживал подобный случай. Очень редко это происходило. Оказалось, что определенная «ручная» функция администратора может вставлять идентификаторы самостоятельно, оставляя счетчик идентификаторов со старым значением. Это реальная опасность с «GENERATED DEFAULT AS IDENTITY». Я подумаю дважды, прежде чем использовать «ПО УМОЛЧАНИЮ» вместо «ВСЕГДА» в следующий раз, когда я определю столбец идентификаторов.
Майкл
4

Я оказался здесь с той же самой ошибкой, которая возникала редко, и было трудно отследить, потому что я искал ее не там, где должен.

Ошибка была JS повторение, которое делало POST к серверу дважды! Поэтому иногда стоит взглянуть не только на ваши представления и формы django (или любого другого веб-фреймворка), но и на то, что происходит на самой лицевой стороне.

andilabs
источник
1

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

db_alias = schema_editor.connection.alias
bulk = []
for item in items:
    bulk.append(MyModel(
        id=item[0],
        value=item[1],
        slug=item[2],
        name=item[3],
    ))

MyModel.objects.using(db_alias).bulk_create(bulk)

Затем в админ-панели я попытался добавить новый элемент и получил:

Первая попытка:

DETAIL:  Key (id)=(1) already exists.

Более поздние попытки:

DETAIL:  Key (id)=(2) already exists.
DETAIL:  Key (id)=(3) already exists.
DETAIL:  Key (id)=(4) already exists.
DETAIL:  Key (id)=(5) already exists.
DETAIL:  Key (id)=(6) already exists.

И, наконец, 7-е и по времени все удачно

Так что я говорю, что, может быть, что-то связано с bulk_create, так как я загрузил туда 6 элементов. Это может быть что-то подобное в вашем проекте Django, вызывающее это.

Django 1.9 PostgreSQL 9.3.14

Бартош Домбровски
источник