Django cache.set(), вызывающий дублируемую ключевую ошибку
Мой сайт Django недавно начал бросать ошибки из моего кода кеширования, и я не могу понять, почему...
Я звоню:
from django.core.cache import cache
cache.set('blogentry', some_value)
И ошибка, вызванная Django, такова:
TransactionManagementError: This code isn't under transaction management
Но, глядя на журналы базы данных PostgreSQL, похоже, эта ошибка возникает из-за этой ошибки:
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"
В жизни я не могу понять, почему Django пытается сделать INSERT вместо UPDATE. Любые мысли?
Ответы
Ответ 1
Это типичная гонка. Он проверяет, существует ли ключ, который вы вставили; если это не так, это делает вставку, но кто-то другой может вставить ключ между счетчиком и вставкой. Транзакции не мешают этому.
Код, похоже, ожидает этого и попытается справиться с этим, но когда я посмотрел на код для обработки этого случая, я сразу увидел, что он был сломан. Сообщено здесь: http://code.djangoproject.com/ticket/11569
Я настоятельно рекомендую придерживаться бэкэнда memcache.
Ответ 2
Код в core/cache/backend/db.py частично читается:
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)])
Итак, я бы сказал, что вы делаете INSERT INTO вместо UPDATE, потому что результат оценивается как false. По какой-то причине cursor.fetchone() возвращает 0 строк, если на самом деле там есть.
Если вы не можете сломать в отладчике здесь, я бы поставил инструкции трассировки в источник, чтобы подтвердить, что это происходит на самом деле.
Ответ 3
Я решил эту проблему, создав собственный буферный сервер, переопределив функцию _base_set() и изменив инструкцию INSERT INTO следующим образом. Этот трюк SQL предотвращает появление INSERT в случае, если cache_key уже существует.
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])