← 返回题目列表

Django 中如何使用事务?atomic 有什么作用?

高频 中等 第 10 / 27 题 更新于 2026/07/27
Django事务atomic数据一致性

简化版

Django 默认是自动提交模式,每条数据库写操作通常会立即提交。transaction.atomic() 用来声明一个事务块:代码块正常结束就提交,抛出异常就回滚。它可以嵌套,内层通常通过保存点实现局部回滚。

详细版

常见用法:

from django.db import transaction

def transfer(from_account, to_account, amount):
    with transaction.atomic():
        from_account.balance -= amount
        from_account.save()
        to_account.balance += amount
        to_account.save()

事务适合包住必须同时成功或同时失败的一组操作,例如转账、下单扣库存、创建订单与明细。关键点:

  • Django 默认 autocommit,单条写操作完成后提交;
  • atomic() 开启事务边界;
  • 块内抛异常会回滚;
  • 捕获异常时要小心,不要把异常吞掉后让事务误以为成功;
  • 嵌套 atomic() 通常使用数据库保存点;
  • 可配合 select_for_update() 做行级锁,避免并发更新冲突。

面试时要能说出事务的目的:保证原子性和一致性,而不是提升性能。

完整版教学

一、为什么 Web 项目离不开事务

很多业务不是一条 SQL 能完成的。比如下单:

  1. 创建订单;
  2. 创建订单明细;
  3. 扣减库存;
  4. 写支付流水;
  5. 发放优惠券或更新账户余额。

如果前两步成功、第三步失败,数据就会半截成功,业务状态会混乱。事务的价值是把这些操作放进同一个边界里:要么全部提交,要么全部回滚。

二、Django 默认的 autocommit

Django 默认使用自动提交模式。也就是说,如果你没有显式开启事务,每次 ORM 写入通常会在语句完成后提交。

user.name = "Tom"
user.save()  # 通常会提交

这种模式对普通请求很方便,但遇到多步骤一致性操作时,就要使用 atomic()

三、atomic 的基本使用

atomic() 可以作为上下文管理器:

from django.db import transaction

with transaction.atomic():
    Order.objects.create(user=user)
    Stock.objects.filter(id=sku_id).update(count=F("count") - 1)

也可以作为装饰器:

@transaction.atomic
def create_order(request):
    ...

上下文管理器更灵活,可以只包住关键数据库操作;装饰器更方便,但容易把整个视图都包进事务里,导致事务时间过长。

四、异常处理的坑

事务是否回滚,主要看 atomic 块是否感知到异常:

try:
    with transaction.atomic():
        do_step1()
        do_step2()
except Exception:
    handle_error()

这种写法通常没问题,异常离开 atomic 块后会触发回滚。

危险写法是:

with transaction.atomic():
    try:
        do_step1()
        do_step2()
    except Exception:
        pass

如果异常被吞掉,atomic 可能认为代码块正常结束,从而提交前面已经执行的操作。除非你明确知道怎么处理事务状态,否则不要在事务块内部随意吞异常。

五、嵌套事务与保存点

Django 的 atomic() 可以嵌套:

with transaction.atomic():
    create_order()
    try:
        with transaction.atomic():
            create_coupon()
    except CouponError:
        pass

内层通常使用保存点。内层失败可以回滚到保存点,而外层事务仍可继续。但最终外层如果失败,整个事务仍会回滚。

六、并发更新要配合锁或条件更新

事务不等于自动解决所有并发问题。例如两个请求同时扣库存:

stock = Stock.objects.get(id=1)
stock.count -= 1
stock.save()

这可能出现丢失更新。可以使用:

from django.db.models import F

Stock.objects.filter(id=1, count__gte=1).update(count=F("count") - 1)

或者在事务中使用 select_for_update()

with transaction.atomic():
    stock = Stock.objects.select_for_update().get(id=1)
    stock.count -= 1
    stock.save()

select_for_update() 会请求数据库行级锁,但它依赖数据库事务和具体数据库支持情况。

七、事务边界应该放在哪里

atomic 的难点不是会不会写装饰器,而是事务边界怎么设计。事务范围太小,不能保证一组相关操作的一致性;事务范围太大,会长时间持有数据库连接和锁,影响并发性能。一般原则是:把必须一起成功或一起失败的数据库操作放进同一个事务,把外部慢操作、网络请求、复杂计算尽量放在事务外。

例如创建订单时,订单、订单明细、库存扣减可能需要在同一个事务中;但发送短信、调用第三方支付、推送消息不应该直接放在事务里长时间阻塞。更好的方式是事务提交后再触发异步任务,或者使用 outbox/event 表保证消息可靠投递。

Django 提供 transaction.on_commit(),可以在事务成功提交后执行回调:

from django.db import transaction

with transaction.atomic():
    order = Order.objects.create(...)
    transaction.on_commit(lambda: send_order_created_task(order.id))

这样可以避免事务回滚了,消息却已经发出去的尴尬问题。

八、常见追问:select_for_update 和并发一致性

事务题经常和并发修改一起考。比如两个请求同时扣库存,如果只是先查库存再保存,可能出现超卖。可以使用数据库行锁:

with transaction.atomic():
    product = Product.objects.select_for_update().get(id=product_id)
    if product.stock <= 0:
        raise ValueError("库存不足")
    product.stock -= 1
    product.save(update_fields=["stock"])

select_for_update() 会在事务中锁住查询到的行,直到事务提交或回滚。它适合强一致的并发修改场景,但也会带来锁等待和死锁风险。面试时要补充:锁要尽量精确,事务要尽量短,访问资源顺序要一致。

还可以用 F() 表达式做原子更新:

from django.db.models import F

Product.objects.filter(id=product_id, stock__gt=0).update(stock=F("stock") - 1)

这类写法让更新在数据库层完成,避免把旧值读到应用层再写回。事务、行锁、原子更新要结合业务一致性要求选择。

九、常见误区与追问

场景推荐做法原因
多步写入必须一致transaction.atomic()成功一起提交,失败一起回滚
并发扣库存select_for_update 或条件更新避免读旧值再覆盖
提交后发消息transaction.on_commit()避免回滚后外部副作用已发生
  • 误区:用了 atomic 就解决所有并发问题。 atomic 保证事务边界内一致提交或回滚,并发覆盖还要结合锁、隔离级别或条件更新。
  • 误区:在 atomic 内捕获异常后继续执行一定安全。 如果异常破坏事务状态,继续查询或写入可能触发事务错误;通常应让异常离开事务块或在外层处理。
  • 误区:事务越大越安全。 事务过大会延长锁持有时间,增加锁等待和死锁风险,边界应尽量短而明确。
  • 追问:嵌套 atomic 会怎样? 外层通常是真事务,内层多用保存点;内层回滚可以回到保存点,但最终提交仍由外层事务决定。
  • 追问:什么时候用 on_commit? 当事务成功后才应该触发外部副作用,如发消息、清缓存、调用异步任务时,用 on_commit 更稳。
  • 追问:select_for_update 要注意什么? 必须在事务中使用,锁住匹配行直到提交或回滚;要控制锁范围、访问顺序和事务耗时。

记忆钩子:atomic 管“一起成败”,锁和条件更新管“并发修改”,on_commit 管“提交后副作用”;三者经常连着考。

十、加强记忆

Django 默认自动提交,transaction.atomic() 用来划出“必须一起成功或一起失败”的事务边界。正常结束提交,异常离开块时回滚;嵌套时常用保存点。事务负责一致性,并发安全还要结合行锁、条件更新和合理的事务范围。