Django cache.set () causando error de clave duplicada

Mi sitio de Django recientemente comenzó a lanzar errores de mi código de caché y no puedo entender por qué …

Yo lo llamo:

from django.core.cache import cache cache.set('blogentry', some_value) 

Y el error lanzado por Django es:

 TransactionManagementError: This code isn't under transaction management 

Pero mirando los registros de la base de datos PostgreSQL, parece derivar de este error:

 STATEMENT: INSERT INTO cache_table (cache_key, value, expires) VALUES (E'blogentry', E'pickled_version_of_some_value', E'2009-07-27 11:10:26') ERROR: duplicate key value violates unique constraint "cache_table_pkey" 

Por mi vida, no puedo entender por qué Django está tratando de hacer un INSERT en lugar de una ACTUALIZACIÓN. ¿Alguna idea?

Esa es una carrera típica. Comprueba si la clave que insertaste existe; si no lo hace, hace una inserción, pero alguien más puede insertar la clave entre el recuento y la inserción. Las transacciones no impiden esto.

El código parece esperar esto y tratar de manejarlo, pero cuando miré el código para manejar este caso, inmediatamente pude ver que estaba roto. Reportado aquí: http://code.djangoproject.com/ticket/11569

Recomiendo encarecidamente seguir el backend de memcache.

El código en core / cache / backend / db.py se lee en parte:

 cursor.execute("SELECT cache_key, expires FROM %s WHERE cache_key = %%s" % self._table, [key]) try: result = cursor.fetchone() if result and (mode == 'set' or (mode == 'add' and result[1] < now)): cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % self._table, [encoded, str(exp), key]) else: cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, [key, encoded, str(exp)]) 

Así que diría que estás haciendo INSERT INTO en lugar de UPDATE porque el resultado se evalúa como falso. Por alguna razón, cursor.fetchone () devuelve 0 filas cuando en realidad hay una allí.

si no puede interrumpir un depurador aquí, pondría declaraciones de seguimiento en la fuente para confirmar que esto realmente está sucediendo.

Resolví este problema creando un backend de caché personalizado, reemplazando la función _base_set () y cambiando la instrucción INSERT INTO de esta manera. Este truco de SQL evita que ocurra INSERT en el caso de que la clave de caché ya exista.

 cursor.execute("INSERT INTO %s (cache_key, value, expires) SELECT %%s, %%s, %%s WHERE NOT EXISTS (SELECT 1 FROM %s WHERE cache_key = %%s)" % (table, table), [key, encoded, connections[db].ops.value_to_db_datetime(exp), key])