You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django 1.11+PostgreSQL事务内是否存在竞态条件?

竞态条件分析与解决方案

你担心的点确实是对的——这段代码存在竞态条件,而且当前的transaction.atomic()配置没法完全解决这个问题,我来拆解一下:

为什么会有竞态条件?

看你的代码逻辑:

  1. 先从数据库查询出一批Account对象(accounts = Account.objects.filter(id__in=account_ids))
  2. 循环每个account时,先读取了account.hours的值,再进入原子事务
  3. 事务内用这个预先读取的account.hours创建日志,然后把该账户的hours更新为0

假设有两个请求同时处理同一个账户ID:

  • 请求A先查询到账户的hours是10,进入事务但还没执行更新
  • 请求B在请求A更新前,也查询到同一个账户的hours是10,进入事务
  • 最后两个请求都创建了diff=-10的日志,并且都把账户hours设为0

结果就是:账户实际只应该被扣10小时,但日志里记录了两次扣10,数据一致性被破坏了。问题出在account.hours是在事务外读取的,这个值不是事务内的最新状态。

当前的transaction.atomic()能解决什么?

它能保证事务内的两个操作(创建MyLog和更新Account)是原子的——要么两个都成功,要么都失败,不会出现“日志创建了但账户没更新”或者反过来的情况。但它没法解决事务开始前读取数据带来的竞态,因为读取操作不在事务的原子范围内。

怎么修复这个问题?

有两种常用的可靠方案:

方案1:在事务内重新获取账户数据并加锁

把读取账户数据的操作放到事务内,同时用select_for_update()给行加锁,防止其他事务修改:

from django.db import transaction

def write_off(account_ids):
    for account_id in account_ids:
        with transaction.atomic():
            # 在事务内加锁查询最新的账户数据
            account = Account.objects.select_for_update().get(pk=account_id)
            MyLog.objects.create(
                hours=0,
                operation_type=LOG_OPERATION_WRITE_OFF,
                diff=-account.hours,
            )
            Account.objects.filter(pk=account_id).update(hours=0)

select_for_update()会锁定查询到的行,直到当前事务结束,其他事务想要修改或者加锁同一行时会被阻塞,这样就能保证读取的account.hours是最新的,而且不会有其他事务在中间修改它。

方案2:用F表达式优化先读后写逻辑

如果业务允许,也可以用Django的F()表达式直接更新,避免先读取数据再更新的竞态(不过如果需要记录具体扣除数值,方案1更稳妥):

from django.db import transaction, F

def write_off(account_ids):
    for account_id in account_ids:
        with transaction.atomic():
            # 先加锁获取旧值,确保日志记录准确
            account = Account.objects.select_for_update().get(pk=account_id)
            # 用F表达式更新(这里其实直接设为0也可以,F表达式适合增减场景)
            Account.objects.filter(pk=account_id).update(hours=0)
            MyLog.objects.create(
                hours=0,
                operation_type=LOG_OPERATION_WRITE_OFF,
                diff=-account.hours,
            )

总结一下:你担心的竞态条件确实存在,当前的transaction.atomic()没覆盖到事务外的读取操作,需要把读取操作放到事务内并加锁,才能彻底避免数据不一致的问题。

内容的提问来源于stack exchange,提问作者v18o

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:54:17