我有两个型号
LongJob(models.Model):
pass
Report(models.Model):
job = models.OneToOneField('LongJob', related_name='report')
我首先在视图中创建一个作业:
job = LongJob.objects.create()
然后我将job
传递给Celery任务,我得到或创建一个报告:
@shared_task
def celery_task(job):
report, created = Report.objects.get_or_create(job=job)
有时,get_or_create
来电会引发以下IntegrityError
:
IntegrityError: insert or update on table "report" violates foreign key constraint "job_id_c7400e5ba78c00c_fk_longjob_id"
DETAIL: Key (job_id)=(120057) is not present in table "longjob".
发生错误后,我会检查LongJob
表中是否存在具有所谓不存在ID的作业对象,我确实找到了它。这怎么可能 ?
数据库是Postgresql 9.4。
答案 0 :(得分:1)
Read Committed是PostgreSQL中的默认隔离级别。当一个 transaction使用这个隔离级别,一个SELECT查询(没有FOR UPDATE / SHARE子句仅查看在查询开始之前提交的数据; 它永远不会看到未提交的数据或在此期间提交的更改 并发事务查询执行。实际上是一个SELECT查询 在查询开始的瞬间查看数据库的快照 跑。但是,SELECT确实看到了先前更新执行的效果 在它自己的交易中,即使它们尚未提交。 另请注意,两个连续的SELECT命令可以看到不同的数据, 即使它们属于单一交易,如果是其他交易 事务在第一个SELECT开始之后和之前提交更改 第二个SELECT开始。
https://www.postgresql.org/docs/9.4/static/transaction-iso.html
get_or_create
这个方法是原子假设正确使用,正确的数据库 配置和底层数据库的正确行为。 但是,如果未在数据库级别强制执行唯一性 在get_or_create调用中使用的kwargs(请参阅unique或unique_together), 这种方法容易出现竞争条件,可能导致多重竞争 同时插入具有相同参数的行。
您在django设置中为数据库关闭了自动提交,或者您在事务中运行以下代码。
job = LongJob.objects.create()
您正在一个线程中插入Job
记录,而在另一个线程中插入Report
。但是由于事务隔离级别,芹菜工作者实际上无法看到创建LongJob
实例的作业记录。
get_or_create方法的原子性仅保证第二个线程无法创建具有相同唯一键的相同报表。它不会影响外键引用的对象。
在传递到celery之前提交事务,或者让celery任务等待正在创建的记录(主django线程中的事务将被提交)。