get_or_create()
的操作。例如:
用户提交标签。需要查看该标签是否已经在数据库中。如果不存在,则为其创建一个新记录。如果存在,则只需更新现有记录。
但是查看get_or_create()
的文档时,似乎它不是线程安全的。线程A检查并发现Record X不存在。然后线程B检查并发现Record X不存在。现在线程A和线程B都将创建一个新的Record X。
这必须是一个非常普遍的情况。如何以线程安全的方式处理它?
get_or_create()
的操作。例如:
用户提交标签。需要查看该标签是否已经在数据库中。如果不存在,则为其创建一个新记录。如果存在,则只需更新现有记录。
但是查看get_or_create()
的文档时,似乎它不是线程安全的。线程A检查并发现Record X不存在。然后线程B检查并发现Record X不存在。现在线程A和线程B都将创建一个新的Record X。
这必须是一个非常普遍的情况。如何以线程安全的方式处理它?
自2013年左右开始,get_or_create是原子性的,因此它可以很好地处理并发:
假定正确使用、正确的数据库配置和底层数据库的正确行为,此方法是原子性的。然而,如果在get_or_create调用中使用的kwargs没有在数据库级别上强制执行唯一性(请参见unique或unique_together),则该方法容易出现竞争条件,可能导致同时插入具有相同参数的多个行。
如果您正在使用MySQL,请确保使用READ COMMITTED隔离级别,而不是REPEATABLE READ(默认值),否则您可能会看到get_or_create将引发IntegrityError,但对象将不会在随后的get()调用中出现的情况。
来源:https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create
以下是如何执行的示例:
定义一个模型,其中unique=True:
class MyModel(models.Model):
slug = models.SlugField(max_length=255, unique=True)
name = models.CharField(max_length=255)
MyModel.objects.get_or_create(slug=<user_slug_here>, defaults={"name": <user_name_here>})
... 或者通过使用 unique_togheter:
class MyModel(models.Model):
prefix = models.CharField(max_length=3)
slug = models.SlugField(max_length=255)
name = models.CharField(max_length=255)
class Meta:
unique_together = ("prefix", "slug")
MyModel.objects.get_or_create(prefix=<user_prefix_here>, slug=<user_slug_here>, defaults={"name": <user_name_here>})
注意,非唯一字段在defaults字典中,而不是在get_or_create的唯一字段中。这将确保您的创建是原子性的。
以下是Django中的实现方式:https://github.com/django/django/blob/fd60e6c8878986a102f0125d9cdf61c717605cf1/django/db/models/query.py#L466 - 尝试创建对象,捕获可能的IntegrityError,在这种情况下返回副本。换句话说:在数据库中处理原子性。
这应该是一个很普遍的情况,如何以线程安全的方式处理它?
是的。
在 SQL 中,“标准”解决方案是尝试创建记录。如果成功,就继续前进。
如果创建记录的尝试从 RDBMS 得到“重复”的异常,则执行 SELECT 并继续前进。
然而,Django 具有自己的 ORM 层,并且具有自己的缓存。因此,逻辑被反转,使常见情况可以直接快速地处理,不常见情况(即重复)则会引发罕见的异常。
get_or_create
。我认为这是一个有效的问题。 - A Leeget_or_create
使用了多个字段,我将其移动到了不同的执行路径上,而不是将其留在视图中并在多个模型字段上添加唯一约束。 - A Lee尝试使用transaction.commit_on_success 装饰器来处理在调用get_or_create(**kwargs)时的可调用对象。
"使用commit_on_success装饰器,在函数中使用单个事务处理所有工作。如果函数成功返回,则Django将在该点上提交函数内完成的所有工作。但是,如果函数引发异常,则Django将回滚事务。"
除此之外,在对get_or_create进行并发调用时,两个线程都会尝试使用传递给它的参数获取对象(除了“defaults”参数外,它是在create调用中使用的字典,以防无法检索到任何对象)。如果失败,两个线程都会尝试创建对象,从而导致多个重复对象的出现,除非在数据库级别上实现了使用get()调用中使用的字段的唯一/唯一组合。
这类似于这篇文章 如何解决Django中的竞争条件?
这么多年过去了,但没有人写过关于threading.Lock
的内容。如果由于遗留原因而无法进行unique together
的迁移,则可以使用锁或threading.Semaphore
对象。以下是伪代码:
from concurrent.futures import ThreadPoolExecutor
from threading import Lock
_lock = Lock()
def get_staff(data: dict):
_lock.acquire()
try:
staff, created = MyModel.objects.get_or_create(**data)
return staff
finally:
_lock.release()
with ThreadPoolExecutor(max_workers=50) as pool:
pool.map(get_staff, get_list_of_some_data())
MyModel.objects.get_or_create(**data)
,那么如何避免竞争条件呢? - Sagar AdhikariMyModel.objects.get_or_create(**data)
,并始终使用此函数来达到目的。我的理解正确吗? - Sagar Adhikari