前言

最近服务器出现异常如下:

models.MultipleObjectsReturned: get() returned more than one  -- it returned 2!

我眉头一皱,赶紧看了下代码,发现是get_or_create的锅。

def get_or_create(self, defaults=None, **kwargs):
    """
    Look up an object with the given kwargs, creating one if necessary.
    Return a tuple of (object, created), where created is a boolean
    specifying whether an object was created.
    """
    lookup, params = self._extract_model_params(defaults, **kwargs)
    # The get() needs to be targeted at the write database in order
    # to avoid potential transaction consistency problems.
    self._for_write = True
    try:
        return self.get(**lookup), False
    except self.model.DoesNotExist:
        return self._create_object_from_params(lookup, params)

当并发请求时,这段代码会同时运行,两个进程都没有查询到记录,然后同时创建,下一次再请求时,报MultipleObjectsReturned异常。

解决思路

我们要在并发情况下,不要让这种情况发生,解决思路

  1. 让第二次查询,能查询到
  2. 让第二次插入,无效

具体方案

思路1:

1.加锁 考虑服务是集群的,可以用redis实现分布式锁。

2.队列 把单个请求入队列,同时只有一个请求在执行

思路2:

数据库层面,把get_or_create()里的字段联合作为唯一索引,这样第二次创建的时候就会报重复创建异常。

# 在django model下使用unique_together

class Meta:
    unique_together = [field_1, field_2]

try:
    Model.objects.get_or_create(field_1=1, field_2=2)
except Exception as e:
    logger.info(e)