如何修复Django的get_or_create()中的并发性导致数据重复

时间:2013-08-27 15:05:41

标签: django concurrency nginx

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

我使用uwsgi(4名工作人员)和Django 1.4.5在nginx 1.0.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找不到任何结果并且都创建新对象:< / p>

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

在你提出之前,不,这两个密钥在数据库中不是unique_together

两者都请求从Django返回200状态代码,但nginx会丢弃一个,从而产生409状态代码(Conflict)。 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...

但这也没有钉住它。您对如何处理这些重复请求有任何建议吗?

2 个答案:

答案 0 :(得分:2)

  

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

如果您使用的是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

答案 1 :(得分: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

as seen here