有些時候我們需要對資料庫進行一連串的操作,如果其中某一個操作失敗,那麼其他的操作也要跟著回滾到操作以前的狀態。
舉個例子。某天你到銀行存了 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...')
這就有問題了,前面的 Student
和 Info
都正常儲存進資料庫了,但是 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 協議》,轉載必須註明作者和本文連結