1. 事務
本章我們將通過一個例子來簡要的說明“事務”,這個開發實戰里經常遇到的名詞。事務是如何體現在一個具體的業務和系統的實現里。
事務是通過將一組相關操作組合為一個,要么全部成功要么全部失敗的單元,可以簡化錯誤恢復並使應用程序更加可靠。事務具有4個特性:原子性、一致性、隔離性、持久性。業務事務就是完成具體業務操作后,形成的業務結果;數據庫事務是數據庫產品根據事務的特性實現的相關功能,數據庫事務(Database Transaction) ,是指作為單個邏輯工作單元執行的一系列操作,要么完全地執行,要么完全地不執行,也可以理解成事務在數據庫管理系統的實現。Django作為可以支持數據持久化的框架,也要支持的事務特性機制。
前面章節,我們描述的入庫與庫存的例子,現實世界的業務事務入庫場景是這樣,物品入庫就是倉庫管理員拿着新購進的物品,擺放到相應的貨位上,同時更新庫存登記簿,更新(增加)該物品的庫存數據量,如果過程中倉庫管理員忘記更新庫存登記簿的庫存數據將導致該物品的賬目數據量與該物品的倉庫貨位實際庫存數據量不一致,從而導致混亂。
庫存管理業務系統依據這一業務邏輯規則,我們簡化設計了2張表,入庫單表和物品庫存表來滿足這一業務要求。業務系統的處理邏輯就是當新增的入庫單數據保存到數據庫入庫表后,就必須更新庫存表、該物品的庫存數據,這個過程如果出現某種意外,如更新庫存數據失敗,就必須同時回滾、入庫表的相應操作。這就是事務特性要求的:要么同時成功要么同時失敗。
本章節將繼續以入庫事務這個例子來說明我們在系統中如何設計和實現事務的要求。
1.1. 入庫操作流程
按照我們前面設計的入庫單業務,每次入庫單,其過程至少包括以下二個步數據庫操作:
一、保存入庫單信息到數據庫;
二、更新入庫單物品的當前庫存信息,庫存數量=當前庫存數量+入庫數量。
正常的情況下,這些操作將順利進行,最終交易成功,與交易相關的所有數據庫信息也成功地更新。但是,如果在這一系列過程中任何一個環節出了差錯,例如在更新物品庫存信息時發生異常導致交易失敗。一旦交易失敗,數據庫中所有信息都必須保持交易前的狀態不變,比如最后一步更新物品庫存信息時失敗而導致交易失敗,那么必須保證這筆失敗的交易不影響數據庫的狀態--庫存信息沒有被更新、入庫單也沒有提交成功。
1.1. 現在我們修改我們的代碼來實現新增入庫單時更新我們的庫存信息
我們在AddInStockBill函數中增加如下代碼來更新當前庫存信息:
def AddInStockBill(request): if request.method == 'POST': form = InStockBillForm(request.POST) if form.is_valid(): cd = form.cleaned_data inStockBill = InStockBill() inStockBill.InStockBillCode = cd['InStockBillCode'] inStockBill.InStockDate = cd['InStockDate'] inStockBill.Amount = cd['Amount'] inStockBill.Operator = cd['Operator'] inStockBill.Item = cd['Item'] inventorys = inStockBill.Item.inventory_set.all() currentInventory = Inventory() if (inventorys.count()==0): currentInventory.Item = inStockBill.Item currentInventory.Amount = inStockBill.Amount else: #這里假定只有一個物料,后面我們會根據進程重構代碼 currentInventory = inventorys[0] currentInventory.Amount = currentInventory.Amount + inStockBill.Amount currentInventory.save() #更新庫存 inStockBill.save() #保存入庫單數據
return HttpResponseRedirect('/success/') else: form = InStockBillForm() return render_to_response('InStockAdd.html',{'form': form} ,context_instance = RequestContext(request))
我們現在新增一條入庫單來測試我們的入庫事務是否實現了更新庫存信息。
提交成功后,我們查看數據庫會發現庫存表和入庫單表數據都保存成功了,如下圖:
1.1. 事務失敗
我們數據庫inventory_instockbill表中增加一個非空字段remark來模擬,來模擬更新庫存后,系統在提交入庫單據時出現了異常系統返回失敗了,由於model沒有同步這個字段,入庫單model提交時會引發錯誤,這時根據事務的規則,庫存的更新應該會回滾,就是庫存表數據不更新,入庫單表沒有新的入庫單數據。如下圖:
運行的結果,庫存表庫存數量更新為25,但是入庫單據沒有保存成功,也就是意味着系統運行的結果與業務事務是不符合的,丟失的入庫單據已經導致庫存數量發生變化,我們需要用一定的機制來保證業務事務滿足要求。如下圖:
1.2. Django事務處理
默認情況下,在Django中事務是自動提交的。當我們運行Django內置的模板修改函數時,例如調用model.save()或model.delete()時,事務將被立即提交。這種機制和數據庫的自動提交事務機制類似。記住這里沒有默認的回滾機制,要解決剛才的場景我們須引入Django的數據庫事務控制類django.db.transaction
1.2.1. 在View中實現事務控制
如果想在更細粒度的條件下(例如在一個view函數中)控制事務,我們可以使用django.db.transaction。有兩種用法:
1. 使用裝飾器
from django.db import transaction @transaction.commit_on_success def viewfunc(request): # ... # this code executes inside a transaction # ...
2. 使用context manager
from django.db import transaction def viewfunc(request): # ... # this code executes using default transaction management # ... with transaction.commit_on_success(): # ... # this code executes inside a transaction # ...
1.2.2. 標識使用方法
1. autocommit
使用autocommit裝飾器可以將view函數中的事務還原成Django默認的自動提交模式,無視全局事務的設置。
from django.db import transaction @transaction.autocommit def viewfunc(request): ....
2. commit_on_success()
顧名思義,view函數成功則提交事務,否則回滾。用法同上。
3. commit_manually()
告訴Django我們將自己控制函數中的事務處理。並且要注意,如果在視圖函數中改變了數據庫的數據並且沒有調用commit()或rollback(),那么將拋出TransactionManagementError異常。
from django.db import transaction @transaction.commit_manually def viewfunc(request): ... # You can commit/rollback however and whenever you want transaction.commit() ... # But you've got to remember to do it yourself! try: ... except: transaction.rollback() else: transaction.commit()
1.3. AddInStockBill增加事務標識
from django.db import transaction @transaction.commit_on_success def AddInStockBill(request):
1.4. 事務測試
現在我們重新執行前面事務失敗的例子,來看系統運行的結果是否滿足事務的基本要求。
提交后系統報錯,這我們也會發現數據庫intentory表,提交的數據回滾了,庫存數量沒有更新。入庫單業務實現了正確的業務事務,避免錯誤、混亂的數據提交到數據庫中。
1.5. 小結
本章節我們用入庫的業務例子來闡述如何運行Django的事務機制,以滿足業務事務的要求,例子中我們采用了commit_on_success(),在實際應用中可以根據自己的業務邏輯采用不同的事務標識。
下一個章我們將繼續以入庫單的例子參數如何編寫支持單元測試的代碼例子。






