Django cache.set() 导致重复键错误

4 投票
3 回答
5563 浏览
提问于 2025-04-15 13:12

我最近在我的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操作。有没有什么想法?

3 个回答

0

我通过创建一个自定义的缓存后端来解决这个问题,重写了 _base_set() 函数,并把 INSERT INTO 语句改成了这样。这个 SQL 小技巧可以防止在缓存键已经存在的情况下进行插入操作。

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])
0

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,因为 result 的值是假的。出于某种原因,cursor.fetchone() 返回了 0 行数据,而实际上应该是有一行的。

如果你在这里不能用调试工具来查看情况,我建议你在代码里加一些跟踪语句,确认一下确实发生了这种情况。

4

这就是一个典型的竞争问题。它会检查你插入的键是否存在;如果不存在,就会进行插入,但在检查和插入之间,可能会有其他人也插入这个键。事务并不能防止这种情况发生。

代码似乎是预料到了这种情况,并试图处理它,但当我查看处理这个情况的代码时,我立刻发现它是有问题的。这里有个报告:http://code.djangoproject.com/ticket/11569

我强烈建议使用memcache作为后端。

撰写回答