Django中create()与get()间数据库竞态问题求助
问题分析与解决方案
核心原因是Django默认事务提交时机与异步任务执行的竞态:
Django默认会在请求/函数执行完毕后才自动提交事务,Model.objects.create()返回对象时,数据仅在当前事务内可见,并未提交到PostgreSQL。而django-rq的worker使用独立的数据库连接,PostgreSQL默认READ COMMITTED隔离级别下,无法读取未提交的事务数据,因此异步任务执行时会出现找不到对象的情况。测试环境负载低,事务提交速度快,所以难以复现。
具体解决方案
1. 手动控制事务提交时机
在创建对象后立即提交事务,再触发异步任务:
from django.db import transaction def create_object_and_run_job(): # 用atomic块包裹创建逻辑,块结束自动提交事务 with transaction.atomic(): x = Model.objects.create() # 事务已提交,确保数据落地后再触发任务 job.delay(x_id=x.id)
如果是在视图函数中,也可以显式调用transaction.commit()(注意避免影响其他逻辑)。
2. 使用django-rq的事务提交后触发机制
django-rq原生支持transaction_on_commit参数,确保任务在当前事务提交后再入队:
from django_rq import get_queue def create_object_and_run_job(): x = Model.objects.create() # 传入transaction_on_commit=True,等待事务提交再执行任务 get_queue().enqueue(job, x_id=x.id, transaction_on_commit=True)
也可以在任务装饰器中全局配置:
from django_rq import job @job(transaction_on_commit=True) def your_async_job(x_id): obj = Model.objects.get(id=x_id) # 任务业务逻辑
3. 给异步任务添加重试兜底
即使前面的措施没完全覆盖,给任务加重试机制可以应对偶发的竞态情况:
from django_rq import job from django.core.exceptions import ObjectDoesNotExist # 最多重试3次,每次间隔1秒 @job(retry=3, retry_delay=1) def your_async_job(x_id): try: obj = Model.objects.get(id=x_id) except ObjectDoesNotExist: # 抛出异常触发重试 raise # 后续业务逻辑
4. 排查嵌套事务问题
如果代码中存在嵌套事务(比如外层有atomic块),内层事务的create操作不会立即提交,需要确保外层事务在触发任务前已提交。可以通过transaction.get_connection().in_atomic_block()检查当前是否处于事务中。
排查建议
- 开启Django的事务日志,添加
LOGGING配置跟踪事务提交事件,对比任务执行时间点,确认竞态窗口:LOGGING = { # ...其他配置 'loggers': { 'django.db.backends': { 'level': 'DEBUG', 'handlers': ['console'], 'filters': ['require_debug_true'], }, }, } - 检查生产环境数据库的连接池配置,确认worker的数据库连接是否存在事务复用问题。
内容的提问来源于stack exchange,提问作者Henty
相关产品推荐
相关产品推荐

