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

Django中更新数据库的视图是否需用@transaction.atomic消除数据竞争?

关于视图加@transaction.atomic与数据竞争的问题

只加@transaction.atomic没法彻底解决你遇到的并发更新数据竞争问题,原因和解决办法如下:

为什么当前方案有问题

你的代码是典型的「读-改-写」流程:先查询员工旧工资,计算新工资后再保存。即使加了事务,默认的数据库隔离级别下,两个并发请求可能同时读到同一个旧工资值,各自计算后保存,最终只会有一个更新的结果生效,另一个会被覆盖,导致工资计算错误。

@transaction.atomic的作用是保证视图内的所有数据库操作要么全部成功,要么全部回滚,但它不会自动帮你锁定要修改的行,也无法阻止并发请求读取未提交的旧数据。

正确的解决办法

方法1:使用行锁(配合事务)

在查询员工时加上select_for_update(),这样在事务执行期间,这条员工记录会被锁定,其他并发请求必须等待当前事务完成后才能读取和修改这条记录,避免同时拿到旧值。

修改后的代码:

@api_view(["POST"])
@transaction.atomic
def employee_increase(request):
    logger.info(f"Increase sallary: {request.data}")

    serializer = IdSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)

    # 查询时加行锁
    employee = get_object_or_404(Employee.objects.select_for_update(), pk=request.data["id"])
    old_sallary = employee.sallary

    employee.sallary = int(old_sallary * (1 + int(request.data["increase_percent"]) / 100))
    employee.save()

方法2:使用原子更新操作(更推荐)

直接利用Django的F()表达式把计算逻辑放到数据库层面执行,一次完成更新,彻底跳过「读-改-写」的中间步骤,从根源上避免竞争。这种方式性能也更好,减少了数据库交互次数。

修改后的代码:

from django.db.models import F

@api_view(["POST"])
def employee_increase(request):
    logger.info(f"Increase sallary: {request.data}")

    serializer = IdSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)

    increase_percent = int(request.data["increase_percent"])
    # 直接在数据库层面完成更新
    updated = Employee.objects.filter(pk=request.data["id"]).update(
        sallary=F('sallary') * (1 + increase_percent / 100)
    )
    
    if not updated:
        raise Http404("Employee not found")

这种方式甚至可以不用@transaction.atomic,因为update()本身就是原子性的数据库操作。

总结

@transaction.atomic是有用的,但单独使用不足以解决并发更新的数据竞争。你需要配合行锁,或者更推荐用原子更新操作来彻底避免问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 14:28:33