¿Por qué Postgres genera un valor PK ya utilizado?


20

Estoy usando Django, y de vez en cuando aparece este error:

IntegrityError: el valor duplicado de la clave viola la restricción única "myapp_mymodel_pkey"
DETALLE: La clave (id) = (1) ya existe.

De hecho, mi base de datos Postgres tiene un objeto myapp_mymodel con la clave primaria de 1.

¿Por qué Postgres intentaría usar esa clave primaria nuevamente? ¿O es muy probable que mi aplicación (o el ORM de Django) esté causando esto?

Este problema ocurrió 3 veces más seguidas en este momento. Lo que he encontrado es que cuando no se producen ocurre una o más veces en una fila de una tabla dada, a continuación, otra vez no. Parece suceder para cada mesa antes de que se detenga por completo durante días, ocurriendo durante al menos un minuto más o menos por mesa cuando ocurre, y solo sucede de manera intermitente (no todas las tablas de inmediato).

El hecho de que este error sea tan intermitente (sucedió solo 3 o más veces en 2 semanas; ninguna otra carga en el DB, solo yo probando mi aplicación) es lo que me hace desconfiar de un problema de bajo nivel.


Django declara específicamente que la clave principal es generada por el DBMS a menos que se especifique; ahora, no sé qué estaba haciendo @orokusaky en su código de Python, pero terminé en esta página porque estoy bastante seguro de que no tengo código tratando de usar una clave primaria específica y nunca he visto un DBMS tratando de usar una clave incorrecta.
mccc

Respuestas:


34

PostgreSQL no intentará insertar valores duplicados por sí solo, es usted (su aplicación, ORM incluido) quien lo hace.

Puede ser una secuencia que alimenta los valores al PK establecido en la posición incorrecta y la tabla que ya contiene el valor igual a su nextval(), o simplemente que su aplicación hace lo incorrecto. El primero es fácil de arreglar:

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

El segundo significa depuración.

Django (o cualquier otro marco popular) no restablece las secuencias por sí solo; de lo contrario, tendríamos preguntas similares cada dos días.


¿Vale la pena señalar (también basado en la respuesta de @ andi aquí) sobre los diferentes niveles de aislamiento? Por ejemplo, si la segunda consulta llega antes de que se complete la primera, es posible, dado un escenario en el que no estoy usando transacciones, insertar un registro que resulte en obtener max(id)antes de que se complete la primera consulta, y luego dar como resultado que ambos tengan el mismo resultado?
orokusaki

5

Lo más probable es que intente insertar una fila en una tabla para la cual el valor de secuencia de la columna serial no se actualiza.

Considere la siguiente columna en su tabla, que es la clave principal definida por Django ORM para postgres

id serial NOT NULL

Cuyo valor predeterminado se establece en

nextval('table_name_id_seq'::regclass)

La secuencia solo se evalúa cuando el campo id se establece como en blanco. Pero eso es un problema si ya hay entradas en la tabla.

La pregunta es ¿por qué esas entradas anteriores no activaron la actualización de secuencia? Esto se debe a que el valor de id se proporcionó explícitamente para todas las entradas anteriores.

En mi caso, esas entradas iniciales se cargaron desde accesorios a través de migraciones.

Este problema también puede ser complicado a través de entradas personalizadas con un valor PK aleatorio.

Digamos por ej. Hay 10 entradas en su tabla. Realiza una entrada explícita con PK = 15. Los siguientes cuatro insertos a través del código funcionarían perfectamente bien, pero el quinto generaría una excepción.

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

Gracias por este post He estado depurando un caso como este durante mucho tiempo. Muy raramente ocurrió. Resultó que una función de administrador "manual" específica podía insertar identificadores por sí sola, dejando el contador de identidad con un valor antiguo. Este es un peligro real con "GENERADO POR DEFECTO COMO IDENTIDAD". Lo pensaré dos veces antes de usar "POR DEFECTO" en lugar de "SIEMPRE" la próxima vez que defina una columna de identidad.
Michael

4

Terminé aquí con el mismo error, que ocurría raramente, y era difícil de rastrear, porque lo estaba buscando no donde debería.

¡La falla fue la repetición de JS que estaba haciendo la POST al servidor dos veces! Por lo tanto, a veces vale la pena echar un vistazo no solo a sus vistas y formularios de django (o cualquier otro marco web), sino también a lo que sucede en la parte frontal.


1

Si cosa rara. En mi caso, algo parece estar mal durante la carga de datos en las migraciones. Agregué una migración vacía y escribí las líneas para agregar algunos datos iniciales, 6 registros en mi caso.

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)

Luego, en el panel de administración, intenté agregar un nuevo elemento y obtuve:

Primer intento:

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

Intentos posteriores:

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.

Y finalmente el 7mo y los tiempos son exitosos

Así que estoy diciendo que quizás haya algo relacionado con bulk_create cuando cargué 6 elementos allí. Tal vez sea algo similar en su proyecto de Django que causa eso.

Django 1.9 PostgreSQL 9.3.14

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.