Django事务机制详解:确保数据一致性

目录

[1. 事务的目标与价值(数据一致性场景)](#1. 事务的目标与价值(数据一致性场景))

[2. Django 默认行为(每条 SQL 独立提交)](#2. Django 默认行为(每条 SQL 独立提交))

[3. 如何开启事务(transaction.atomic)](#3. 如何开启事务(transaction.atomic))

[3.1 @transaction.atomic 装饰器](#3.1 @transaction.atomic 装饰器)

[3.2 事务上下文管理器](#3.2 事务上下文管理器)

[4. 异常行为的回滚](#4. 异常行为的回滚)

[4.1 自动回滚](#4.1 自动回滚)

[4.2 强制回滚(手动控制回滚)](#4.2 强制回滚(手动控制回滚))

[5.事务嵌套与 savepoint 的概念](#5.事务嵌套与 savepoint 的概念)

[5.1 Django 自动管理 Savepoint](#5.1 Django 自动管理 Savepoint)

[5.2 手动管理 Savepoint](#5.2 手动管理 Savepoint)

[6.ATOMIC_REQUESTS 的使用场景与代价](#6.ATOMIC_REQUESTS 的使用场景与代价)

[7. 常见误区与最佳实践(过度事务、长事务、并发影响)](#7. 常见误区与最佳实践(过度事务、长事务、并发影响))


1. 事务的目标与价值(数据一致性场景)

(1)什么是事务: 事务的主要目标是确保数据的一致性完整性 ,特别是在发生多个数据库操作时,确保操作要么完全成功,要么完全失败,避免部分操作成功导致系统处于不一致状态。

(2)数据一致性的场景示例

  • 账户转账:假设从账户A转账到账户B,首先从A账户扣款,然后在B账户增加金额。如果在扣款成功后,增加B账户余额时发生错误,可能导致账户A的钱被扣了,而账户B没有得到相应的金额。使用事务能够确保这两个操作要么全部完成,要么全部回滚。

  • 订单支付:在用户支付订单时,涉及多个操作:减少库存、创建订单记录、扣除用户账户余额等。事务确保这些操作要么都成功,要么出现问题时全部回滚。

(3)事务的作用

事务确保了:

  • 原子性(Atomicity):事务中的所有操作要么完全成功,要么完全失败,确保操作的完整性。

  • 一致性(Consistency):事务执行前后,数据保持一致性。

  • 隔离性(Isolation):多个并发事务的执行不会互相影响。

  • 持久性(Durability):一旦事务提交,数据的修改是永久性的。


2. Django 默认行为(每条 SQL 独立提交)

Django 默认开启 AUTOCOMMIT 模式(自动提交模式),这意味着每一条 SQL 语句都是一个独立的事务,执行后立即提交到数据库。

复制代码
# 默认情况下,这两行是独立的事务
user = User.objects.create(username="alice")  # 立即提交
profile = Profile.objects.create(user=user, bio="Hello")  # 立即提交

潜在问题:

  • 如果创建 Profile 时抛出异常(如外键约束失败),User 记录不会回滚,导致数据库中出现"孤儿"用户

在 Django 中,所有的数据库操作(如插入、更新、删除)默认都是自动提交的。如果你在事务中执行多个数据库操作,Django 会自动提交这些操作。但有时你希望将多个操作组合在一起,确保它们要么成功,要么回滚。为了解决这个问题我们可以使用django的事务机制,来确保多个提交要么全部成功要么全部回滚


3. 如何开启事务(transaction.atomic)

Django 提供 transaction.atomic 作为事务控制的核心工具,支持两种使用方式:1.装饰器模式,2.上下文管理器模式

3.1 @transaction.atomic 装饰器

作用: @transaction.atomic 装饰器用于标记一个视图函数为 事务性 ,即视图中的所有数据库操作都将包含在一个事务中。这样,事务中的所有操作要么全部成功,要么在发生异常时回滚。(使用@transaction.atomic 装饰器装饰的视图函数中的所有有关数据库的操作,当视图中的操作全部成功时,Django 会提交事务,将所有操作保存到数据库。一旦出现异常Django 会回滚事务,撤销事务中所有的操作,保持数据库的一致性。)

(1)不使用事务

访问路由为:http://127.0.0.1:8000/book/example1/?error=true

python 复制代码
# 1. Django 默认行为(每条 SQL 独立提交)
# 访问此视图时,如果不开启事务,每一步操作都会立即提交到数据库
def example_1_autocommit(request):
    try:
        # 第一步:创建作者(立即提交)
        author_name = f"author_{int(time.time())}"
        author = Author.objects.create(name=author_name)
        
        # 第二步:尝试创建图书,如果这里抛出异常,作者依然存在
        # 模拟异常:
        if request.GET.get('error') == 'true':
            raise Exception("作者创建后发生错误!")
            
        Book.objects.create(author=author, title="自动提交模式下的图书", price=29.99)
        
        return JsonResponse({"status": "success", "message": "作者和图书已独立创建。"})
    except Exception as e:
        return JsonResponse({"status": "error", "message": str(e), "hint": "检查作者是否在错误发生前已创建。"})

结果:在我们第一步创建好作者之后,假设后面遇到了异常,则异常之后的代码不会执行,即作者成功入库。

从上述结果可以看出,图书相关数据并未添加,是因为遇到了异常,而作者却添加进去了。

(2)使用装饰器

访问路由为:http://127.0.0.1:8000/book/example2/?error=true

python 复制代码
# @transaction.atomic 装饰器
# 整个视图函数在一个事务中,要么全部成功,要么全部失败
@transaction.atomic
def example_2_atomic_decorator(request):
    try:
        author_name = f"atomic_author_{int(time.time())}"
        # 第一步
        author = Author.objects.create(name=author_name)
        
        # 第二步:模拟异常
        if request.GET.get('error') == 'true':
            raise Exception("原子块内部发生错误!")
            
        Book.objects.create(author=author, title="原子装饰器模式下的图书", price=39.99)
        
        return JsonResponse({"status": "success", "message": "事务已提交。"})
    except Exception as e:
        # 为了演示自动回滚,通常不应该在 atomic 块内部吞掉异常,或者应该手动回滚。
        # 但为了 API 友好返回,我们演示手动回滚。
        transaction.set_rollback(True)
        return JsonResponse({"status": "error", "message": str(e), "hint": "由于捕获到异常,事务已手动回滚。"})

在浏览器访问该路由地址后,我们发现数据库并没有新增任何字段,这是因为遇到了异常之后回滚了该视图函数中的所有有关数据库的操作

3.2 事务上下文管理器

除了使用装饰器来控制事务之外,我们还可以使用 transaction.atomic() 上下文管理器进行事务管理。这允许我们将多个操作封装在一个事务中。上下文管理器会自动提交事务或回滚事务。

路由地址: http://127.0.0.1:8000/book/example3/?error=true

python 复制代码
# 3.2 事务上下文管理器
# 只有 with 块内的代码在事务中
def example_3_atomic_context(request):
    try:
        with transaction.atomic():
            author_name = f"context_author_{int(time.time())}"
            author = Author.objects.create(name=author_name)
            
            if request.GET.get('error') == 'true':
                raise Exception("原子上下文内部发生错误!")
                
            Book.objects.create(author=author, title="原子上下文模式下的图书", price=49.99)
            
        return JsonResponse({"status": "success", "message": "上下文事务已提交。"})
    except Exception as e:
        # 这里捕获到了 with 块抛出的异常,atomic 已经自动回滚了
        return JsonResponse({"status": "error", "message": str(e), "hint": "事务已自动回滚。"})

4. 异常行为的回滚

Django中事务的回滚包括自动回滚和手动回滚

4.1 自动回滚

Django 在使用 transaction.atomic() 时,如果发生 未捕获的异常 ,事务会自动 回滚,撤销所有对数据库的修改。事务保证了原子性,确保了数据的完整性。

路由地址:http://127.0.0.1:8000/book/example4

python 复制代码
# 异常行为的回滚 (自动回滚演示)
# 这是一个不捕获异常的视图,由 Django 默认处理(返回 500),事务会自动回滚
@transaction.atomic
def example_4_exception_rollback(request):
    author_name = f"rollback_author_{int(time.time())}"
    Author.objects.create(name=author_name)
    
    # 强制抛出异常,触发自动回滚
    raise Exception("未处理的异常触发自动回滚!")

结果:由于没有捕获异常,服务器给出500错误码,事务回滚。

4.2 强制回滚(手动控制回滚)

在 Django 中,transaction.set_rollback(True) 方法可以手动标记当前事务为回滚状态,即使异常被捕获,也会强制回滚事务中的所有操作。

路由地址:http://127.0.0.1:8000/book/example5/?rollback=true

python 复制代码
# 强制回滚(手动控制回滚)
# 即使没有异常,也可以根据业务逻辑回滚
def example_5_manual_rollback(request):
    try:
        with transaction.atomic():
            author_name = f"manual_author_{int(time.time())}"
            author = Author.objects.create(name=author_name)
            
            # 捕获异常后的手动回滚
            # 如果我们在 atomic 块内部捕获了异常,Django 默认会认为事务成功并提交。
            # 这种情况下,我们需要显式调用 set_rollback(True) 来确保事务回滚。
            try:
                # 假设这里有一个操作抛出了异常
                if request.GET.get('rollback') == 'true':
                    raise Exception("检测到异常,需要手动回滚!")
                
                Book.objects.create(author=author, title="手动回滚演示图书", price=59.99)
            except Exception as e:
                # 捕获了异常,atomic 块感知不到,默认会提交
                # 所以必须手动回滚
                transaction.set_rollback(True)
                return JsonResponse({"status": "warning", "message": f"捕获异常并手动回滚: {str(e)}"})
            
        return JsonResponse({"status": "success", "message": "事务已提交。"})
    except Exception as e:
        return JsonResponse({"status": "error", "message": str(e)})

什么情况下需要使用transaction.set_rollback(True)呢?

当异常被捕获了,Django默认会认为事务成功并提交,这种情况下我们就需要手动调用 transaction.set_rollback(True)来实现回滚。如果想要测试是否是这样可以在上述代码中注释掉transaction.set_rollback(True),可以发现由于异常被捕获了,即使发生了异常也不会回滚。


5.事务嵌套与 savepoint 的概念

Django 支持 嵌套事务 。在一个事务中,你可以启动子事务,并在子事务失败时回滚子事务,而外部事务继续进行。Django 使用 savepoint 来实现这一功能。每个嵌套事务会创建一个保存点,当内层事务回滚时,只会回滚到保存点的状态,而不会影响外层事务。

5.1 Django 自动管理 Savepoint

场景:外层事务创建一个作者,内层事务创建一本书。如果内层事务失败(例如书名不合法),Django 会自动回滚到内层事务开始前的状态(作者依然存在),外层事务可以选择继续执行其他操作(例如创建一本替补书)。

路由地址:http://127.0.0.1:8000/book/example6/?nested_error=true,结果:作者创建成功,"初始图书"创建失败,但自动补救创建了"恢复后的图书"。

python 复制代码
# 5. 事务嵌套与 savepoint
def example_6_nested_savepoint(request):
    try:
        with transaction.atomic(): # 外层事务
            user_name = f"nested_user_{int(time.time())}"
            user = User.objects.create_user(username=user_name, password="password")
            
            try:
                with transaction.atomic(): # 内层事务 (创建 savepoint)
                    Profile.objects.create(user=user, bio="初始简介")
                    
                    if request.GET.get('nested_error') == 'true':
                        raise Exception("嵌套事务中发生错误!")
                        
            except Exception as e:
                # 内层事务回滚,但外层事务继续
                # 这里我们捕获了异常,所以外层事务不会收到异常
                pass
            
            # 外层事务继续执行其他操作
            # 如果内层回滚了,Profile 创建会被撤销,但 User 创建依然保留(只要外层成功提交)
            # 我们可以验证一下
            profile_exists = Profile.objects.filter(user=user).exists()
            
            if not profile_exists:
                 # 可以在这里做补救措施
                 Profile.objects.create(user=user, bio="恢复后的简介")

        return JsonResponse({"status": "success", "message": "嵌套事务已处理。", "profile_exists": profile_exists})
    except Exception as e:
        return JsonResponse({"status": "error", "message": str(e)})

这就好比我们在玩游戏时的 自动存档 。当你进入一个新的关卡( with transaction.atomic() )时,Django 会自动帮你存个档。如果你在这个关卡里挂了(抛出异常),系统会自动把你送回关卡刚开始的时候(回滚到进入 atomic 之前)。这时候你还在游戏里(外层事务还在),你可以选择换条路走(执行补救代码)。"

5.2 手动管理 Savepoint

场景 :在一个大的事务块中,我们手动创建一个保存点。如果在随后的操作中发生错误,我们可以精确地回滚到这个保存点,而不是回滚整个事务。

路由地址:http://127.0.0.1:8000/book/example7/?error=true,结果:作者创建成功,"保存点前的图书"被手动回滚消失,补救创建了"回滚后的补救图书"。

python 复制代码
# 使用 transaction.savepoint() 手动控制回滚点
def example_7_manual_savepoint(request):
    try:
        with transaction.atomic():
            # 1. 创建作者(这将保留,除非整个 atomic 块失败)
            author_name = f"sp_author_{int(time.time())}"
            author = Author.objects.create(name=author_name)
            
            # 2. 创建保存点 (Savepoint)
            # 返回一个保存点 ID (sid)
            sid = transaction.savepoint()
            
            try:
                # 3. 尝试创建图书
                Book.objects.create(author=author, title="保存点前的图书", price=89.99)
                
                # 模拟异常
                if request.GET.get('error') == 'true':
                    raise Exception("保存点后发生错误!")
                
                # 4. 如果没有异常,我们可以显式提交保存点(释放内存中的保存点记录)
                # 注意:这不会提交整个事务,只是说这个保存点不需要了
                transaction.savepoint_commit(sid)
                message = "保存点已提交,图书创建成功。"
                
            except Exception as e:
                # 5. 发生错误,回滚到保存点
                # 这会撤销 savepoint 之后的所有数据库操作
                transaction.savepoint_rollback(sid)
                
                # 此时作者依然存在,但"保存点前的图书"已被删除
                
                # 执行补救措施
                Book.objects.create(author=author, title="回滚后的补救图书", price=99.99)
                message = f"捕获异常 ({str(e)}),已回滚至保存点并执行补救措施。"
                
        return JsonResponse({"status": "success", "message": message})
    except Exception as e:
        return JsonResponse({"status": "error", "message": str(e)})

"这就像是 手动存档 。在打 Boss 之前,你按了一下保存键( sid = transaction.savepoint() )。然后你冲上去打,如果发现打不过快死了,你手动加载刚才的存档( transaction.savepoint_rollback(sid) )。所有的控制权都在你手里,你可以决定什么时候存,什么时候读档。"


6.ATOMIC_REQUESTS 的使用场景与代价

ATOMIC_REQUESTS 是 Django 的一个配置项,如果设置为 True,Django 会自动为每个请求开启事务。这意味着,每个 HTTP 请求会被当作一个单独的事务来处理。

配置示例:

DATABASES = {

'default': {

'ENGINE': 'django.db.backends.postgresql',

'ATOMIC_REQUESTS': True, # 自动为每个请求开启事务

}

}

使用场景

适用于较为简单的应用,特别是当你希望简化事务管理时,每个请求的操作自动会被封装在一个事务中。

适合使用 ATOMIC_REQUESTS 的场景:

  1. 小型应用:业务逻辑简单,请求处理时间短

  2. 数据一致性要求高:希望确保每个请求要么完全成功,要么完全失败

  3. 简化开发:减少手动管理事务的代码

代价

  • 性能开销:每个请求都会启动一个事务,这会增加额外的性能开销,特别是在高并发的情况下。

  • 锁问题:长时间的事务可能会导致数据库的锁定,影响其他请求的处理。


7. 常见误区与最佳实践(过度事务、长事务、并发影响)

7.1 过度使用事务

并非每个操作都需要事务,特别是简单的查询或更新,过度使用事务会导致性能下降

错误示例:

python 复制代码
# 错误:为简单查询使用事务
@transaction.atomic
def get_user_profile(user_id):
    """
    不必要的:只读查询不需要事务
    """
    user = User.objects.get(id=user_id)
    return user.profile

正确做法:

python 复制代码
# 正确:只读查询不需要事务
def get_user_profile(user_id):
    """
    只读查询,不需要事务
    """
    user = User.objects.get(id=user_id)
    return user.profile

7.2 长事务

长时间运行的事务会导致锁定资源,阻塞其他操作,影响数据库的并发能力。事务应尽量缩短时间,避免长时间占用数据库连接。

错误示例:

python 复制代码
# 错误:长事务包含耗时操作
@transaction.atomic
def process_order_with_external_api(order_id):
    """
    错误:在事务中调用外部 API
    """
    order = Order.objects.get(id=order_id)
    
    # 调用外部 API(可能需要几秒钟)
    payment_result = call_external_payment_api(
        amount=order.total_price
    )
    
    # 更新订单状态
    order.status = 'paid'
    order.save()
    
    return payment_result

正确做法:

python 复制代码
# 正确:将耗时操作放在事务外
def process_order_correct(order_id):
    """
    正确:将耗时操作放在事务外
    """
    order = Order.objects.get(id=order_id)
    
    # 在事务外调用外部 API
    payment_result = call_external_payment_api(
        amount=order.total_price
    )
    
    if payment_result['success']:
        # 只在需要时开启事务
        with transaction.atomic():
            order.status = 'paid'
            order.transaction_id = payment_result['transaction_id']
            order.save()
    
    return payment_result

7.3 忽略并发问题

在高并发环境下,多个事务可能会互相影响,出现"脏读"、"不可重复读"等问题。可以考虑使用隔离级别来控制这些问题。

解决方案 1:使用 select_for_update

解决方案 2:使用 F 表达式

相关推荐
BHXDML1 小时前
操作系统实验:(七)动态分区分配方式的模拟
开发语言·数据库·操作系统
LaughingZhu1 小时前
Product Hunt 每日热榜 | 2026-02-19
数据库·人工智能·经验分享·神经网络·chatgpt
Howie Zphile1 小时前
# 组织增熵与全面预算管理的持续优化
java·大数据·数据库
清水白石0081 小时前
从频繁“握手”到高效通行:Python 数据库连接池深度解析与调优实战
开发语言·数据库·python
l1t1 小时前
DeepSeek总结的DuckDB爬虫(crawler)扩展
数据库·爬虫
datalover2 小时前
spring security自定义表结构处理
数据库·python·spring
hhzz2 小时前
【回顾MySQL的SQL基础开发与应用】SQL分类与数据类型、视图、触发器以及存储过程与事件
数据库·sql·mysql
Howie Zphile2 小时前
FRAPPE v16 +postgresql +insight+wiki安装
数据库·postgresql·frappe·全面预算
枷锁—sha2 小时前
【SRC】SSRF (服务端请求伪造) 专项挖掘与实战笔记
数据库·笔记·安全·网络安全