2009-07-27 1 views
4

Мой сайт Джанго недавно начал бросать ошибки из моего кода кэширования, и я не могу понять, почему ...Джанго cache.set() вызывает дубликат ключа ошибки

Я называю:

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

И ошибка брошена Джанго является:

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. Есть предположения?

+0

Не кэшируется ли база данных, чтобы победить цель кеширования? – thedz

+4

Зависит от того, что вы кешируете. –

+0

Я отредактировал его на «blogentry», но на самом деле он кэширует все множество связанных данных для виджета боковой панели блога. –

ответ

4

Это типичная гонка. Он проверяет, существует ли ключ, который вы вставили; если это не так, это делает вставку, но кто-то другой может вставить ключ между счетчиком и вставкой. Транзакции не мешают этому.

Код, кажется, ожидает этого и попытается справиться с этим, но когда я посмотрел на код для обработки этого случая, я сразу увидел, что он был сломан. Сообщается здесь: http://code.djangoproject.com/ticket/11569

Я настоятельно рекомендую придерживаться бэкэнда memcache.

+0

Я прочитал источник для бэкэнда db-кеша и думал о том же, что и о открывшемся вами билете. Рад, что не только я чувствую, что здесь что-то не хватает. Вместо этого мы создадим memcached. Чтение источника для mecached backend гораздо более обнадеживает. Благодаря! –

0

код в ядро ​​/ кэш/бэкэнда/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, потому что результат оценивается как ложное , По какой-то причине cursor.fetchone() возвращает 0 строк, если на самом деле там есть.

Если вы не можете сломать в отладчике здесь, я бы поместил инструкции трассировки в источник, чтобы подтвердить, что это происходит на самом деле.

+1

Да, я читал источник снова и снова ... как заметил Гленн, реальным виновником здесь является то, что бэкэнд имеет потенциальные проблемы параллелизма, когда второй оператор INSERT может появиться после того, как select возвращает 0, в результате чего исходный кеш .set(), который затем вызывает нерабочий вызов transaction.rollback(). –

0

Я решил эту проблему, создав собственный буферный сервер, переопределив функцию _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]) 
Смежные вопросы