应分层捕获具体异常而非宽泛的DatabaseError,优先用select_for_update()主动加锁,辅以针对性重试;对原子增减操作使用F()表达式提升性能,复杂业务逻辑仍需锁或重试机制。

捕获 DatabaseError 还是更具体的 OperationalError?
Django 本身不定义“并发更新冲突异常”这个类型,底层实际抛出的是数据库驱动的原生异常。PostgreSQL 触发 SerializationFailure(属于 OperationalError),MySQL 在 REPEATABLE READ 下可能抛 IntegrityError 或 OperationalError,SQLite 则通常表现为 DatabaseError。直接捕获 DatabaseError 范围太大,容易掩盖连接断开、表不存在等真正需要告警的问题;推荐按数据库类型分层捕获:from django.db import OperationalError, IntegrityError,再结合 if "serialization" in str(e).lower() 做轻量判断。
用 select_for_update() 主动加锁比靠异常重试更可控
依赖“先更新再捕获异常再重试”的模式,在高并发下会放大冲突概率,且业务逻辑可能已执行副作用(如发消息、扣库存未回滚)。更稳妥的做法是在读取时就加行级锁:
from django.db import transaction
<p>with transaction.atomic():
try:
obj = MyModel.objects.select_for_update().get(id=123)
obj.counter += 1
obj.save()
except DatabaseError as e:</p><h1>极少数情况锁等待超时,此时才需兜底</h1><pre class='brush:python;toolbar:false;'> if "lock timeout" in str(e):
raise ValueError("资源繁忙,请稍后重试")select_for_update() 在 PostgreSQL/MySQL 上生效,但注意:它要求必须在 transaction.atomic() 块中使用;对 MySQL,表引擎必须是 InnoDB;SQLite 不支持该方法,会静默降级为普通查询。
重试逻辑别写在视图里,用装饰器或工具函数封装
手动写 for i in range(3): try: ... except ...: time.sleep(0.1) 容易漏掉事务边界、重复执行非幂等操作。Django 3.1+ 提供了 django.db.transaction.atomic(using=None, savepoint=True, durable=False) 的嵌套控制能力,配合自定义重试更安全:
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
常见错误现象:TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block. —— 这是因为在 atomic 块内捕获异常后没显式回滚,后续查询被拒绝。
实操建议:
- 用
@retry_on_conflict(max_retries=3, delay=0.1)这类装饰器,内部确保每次重试都进入新事务 - 重试前检查是否真为并发冲突(例如匹配
"could not serialize access due to concurrent update"),避免把语法错误也重试 - 记录重试次数到日志,超过阈值触发告警,而不是无限循环
UPDATE … RETURNING 和 F() 表达式能绕过部分冲突场景
如果只是原子增减字段(如计数器、余额),优先用数据库原生命令而非“查-改-存”三步:
from django.db.models import F
<p>updated = MyModel.objects.filter(id=123).update(counter=F('counter') + 1)
if updated == 0:</p><h1>记录不存在,不是并发问题,是业务逻辑错误</h1><pre class='brush:python;toolbar:false;'>raise MyModel.DoesNotExist这种写法不触发 ORM 的 select-for-update 流程,也不进入 Python 层的竞态判断,性能更高。但缺点是无法获取更新后的完整对象(除非用 PostgreSQL 的 RETURNING 配合 raw SQL),也无法在更新前后做复杂校验(如余额不能为负)。
真正难处理的是“读取状态 → 判断业务规则 → 更新”的复合操作,这时候锁或重试仍是必要手段。关键在于别假设所有数据库异常都是并发导致的,先区分错误来源,再决定是加锁、重试,还是换用原子 SQL。

















