Django處理事務:transaction

ConnorK發表於2021-03-06

有些時候我們需要對資料庫進行一連串的操作,如果其中某一個操作失敗,那麼其他的操作也要跟著回滾到操作以前的狀態。

舉個例子。某天你到銀行存了 100 塊錢,所以你的賬戶的資料庫表就應該減去 100 塊,而銀行的賬戶上增加 100 塊。但如果資料庫在執行銀行賬戶增加 100 塊時操作失敗了,豈不是平白無故損失掉 100 塊錢,那你不得把銀行屋頂給拆了。

這種情況下就需要用到事務這個概念了,即把一組操作捆綁到一起,大家生死與共,要麼都成功,要麼都失敗,結成人民統一戰線。

Django 裡如何實現事務?看下面的例子:

# models.py
from django.db import models


class Student(models.Model):
    """學生"""
    name = models.CharField(max_length=20)


class Info(models.Model):
    """學生的基本情況"""
    age = models.IntegerField()


class Address(models.Model):
    """學生的家庭住址"""
    home = models.CharField(max_length=100)

有三個模型,Student 為學生、Info 為學生的基本情況、Address 為學生的住址。假設這三個模型必須同時建立,否則資料就是不完整的。

我們可以這樣寫檢視:

def create_student(request):
    student = Student.objects.create(name='張三')
    info = Info.objects.create(age=19)
    address = Address.objects.create(home='北京')

    return HttpResponse('Create success...')

很正常對吧。接下來讓程式故意引發錯誤:

def create_student(request):
    student = Student.objects.create(name='張三')
    info = Info.objects.create(age=19)

    # 引發錯誤
    oh_my_god = int('abc')

    address = Address.objects.create(home='北京')

    return HttpResponse('Create success...')

這就有問題了,前面的 StudentInfo 都正常儲存進資料庫了,但是 Address 卻由於前一句報錯而沒有執行建立,因此學生資訊就變成了不完整的垃圾資料了。

解決辦法就是把檢視函式中的資料操作轉化為事務

from django.db import transaction

# 注意這個裝飾器
@transaction.atomic
def create_student(request):
    student = Student.objects.create(name='張三')
    info = Info.objects.create(age=19)

    oh_my_god = int('abc')

    address = Address.objects.create(home='北京')

    return HttpResponse('Create success...')

這就非常不同了。無論檢視裡哪一個資料庫操作失敗或是沒有執行,那麼其他的操作也都會回滾到操作前的狀態。也就是說上面這段程式碼中的三個模型,都沒有儲存成功。

有的時候檢視裡有很多的資料操作,如果我只想回滾其中一部分為事務也是有辦法的:

from django.db import transaction

@transaction.atomic
def create_student(request):
    student = Student.objects.create(name='張三')

    # 回滾儲存點
    save_tag = transaction.savepoint()

    try:
        info = Info.objects.create(age=19)

        # 引發錯誤
        oh_my_god = int('abc')

        address = Address.objects.create(home='北京')
    except:
        # 回滾到 save_tag 的位置
        transaction.savepoint_rollback(save_tag)

    return HttpResponse('Create success...')

上面的程式碼執行之後,Student 表會成功儲存,而另外兩張表則都會失敗。使用 try 的好處在於前端能正常執行。

除此之外,還有另一種方法可以將檢視中的事務進行分組,實現更細膩的控制:

# 裝飾器不要了
# @transaction.atomic
def create_student(request):
    student = Student.objects.create(name='張三')

    # 事務
    with transaction.atomic:
        info = Info.objects.create(age=19)

        # 引發錯誤
        oh_my_god = int('abc')

        address = Address.objects.create(home='北京')

    return HttpResponse('Create success...')

效果是差不多的,僅有 Student 成功儲存。

還有最後一個大殺器。如果你想讓所有的資料庫操作都是事務,那就在 settings.py 裡配置:

# settings.py

# 以 sqlite 為例
DATABASES = {
    'default': {
        'ENGINE': ...,
        'NAME': ...,
        # 加上這條
        'ATOMIC_REQUESTS': True,
    }
}

然後可以用 non_atomic_requests 標記不需要成為事務的檢視:

@transaction.non_atomic_requests
def create_student(request):
    ...

另外,類檢視也是可以成為事務的:

class CreateStudent(View):
    @transaction.atomic
    def get(self, request):
        ...

最後總結一下,並非任意對資料庫的操作序列都是事務。資料庫事務擁有ACID特性

  • 原子性(Atomicity):事務作為一個整體被執行,包含在其中的對資料庫的操作要麼全部被執行,要麼都不執行。
  • 一致性(Consistency):事務應確保資料庫的狀態從一個一致狀態轉變為另一個一致狀態。一致狀態的含義是資料庫中的資料應滿足完整性約束。
  • 隔離性(Isolation):多個事務併發執行時,一個事務的執行不應影響其他事務的執行。
  • 永續性(Durability):已被提交的事務對資料庫的修改應該永久儲存在資料庫中。
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章