3

我有一个相当复杂的问题,导致我的数据库中出现重复记录。

我在 nginx 1.0.5 上运行 uwsgi(4 个工作人员)和 Django 1.4.5。问题是一些客户端对同一路径发出重复请求,如下面的 nginx 日志所示:

10.205.132.51 - - [26/Aug/2013:16:59:41 -0300] "GET /path/to/ HTTP/1.1" 499 0 "http://mydomain.com.br/path/" "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20100101 Firefox/21.0"
10.205.132.51 - - [26/Aug/2013:16:59:41 -0300] "GET /path/to/ HTTP/1.1" 200 7372 "http://mydomain.com.br/path/" "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20100101 Firefox/21.0"

这些请求正在同时处理,在下面这个视图的情况下,我进入了一个竞争条件,两者都get_or_create没有找到结果并且都创建了一个新对象

with transaction.commit_on_success():
    f, created = cls.objects.get_or_create(
        key1=value1,
        key2=value2,
        defaults={...})

在你问之前,不,这两个键不是unique_together数据库中。

两个请求都200从 Django 返回状态码,但是 nginx 丢弃了一个,导致409状态码(冲突)。这transaction.commit_on_success()部分是减少的尝试,但没有解决问题。

我还尝试了一个基于缓存的锁,使用这个函数:

@contextmanager
def cache_exclusive(name, timeout=10):
    """ found at http://coffeeonthekeyboard.com/simple-out-of-process-lock-with-python-and-memcached-2-985/ """
    key = 'cache_lock:%s' % name
    lock = cache.add(key, True, timeout=timeout)  # Fails if key already exists.
    yield lock  # Tell the inner block if it acquired the lock.
    if lock:  # Only clear the lock if we had it.
        cache.delete(key)

以及具有此用法的唯一名称:

    with cache_exclusive('key1 and key2') as granted:
        if not granted:
            return
        # do the get_or_create stuff...

但这也没有解决。您对如何处理这些重复请求有什么建议吗?

4

2 回答 2

2

假设正确使用、正确的数据库配置和正确的底层数据库行为,此方法是原子的。但是,如果在 get_or_create 调用中使用的 kwargs 没有在数据库级别强制执行唯一性(请参阅 unique 或 unique_together),则此方法容易出现竞争条件,这可能导致同时插入具有相同参数的多行。

如果您使用 MySQL,请务必使用 READ COMMITTED 隔离级别而不是 REPEATABLE READ(默认),否则您可能会看到 get_or_create 会引发 IntegrityError 但对象不会出现在后续 get() 调用中的情况。

最后,关于在 Django 视图中使用 get_or_create() 的一句话:请确保仅在 POST 请求中使用它,除非您有充分的理由不 GET 请求不应该对数据产生任何影响;每当对页面的请求对您的数据产生副作用时,请使用 POST。有关更多信息,请参阅 HTTP 规范中的安全方法。

https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

于 2013-09-10T18:11:52.700 回答
0
 @transaction.commit_on_success
 def my_get_or_create(...):
     try:
         obj = MyObj.objects.create(...)
     except IntegrityError:
         transaction.commit()
         obj = MyObj.objects.get(...)
     return obj

如此处所见

于 2013-09-09T19:07:42.540 回答