標籤:
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_successdef viewfunc(request):# ...# this code executes inside a transaction# ...
2. 使用context manager
from django.db import transactiondef 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.autocommitdef viewfunc(request):....
2. commit_on_success()
顧名思義,view函數成功則提交事務,否則復原。用法同上。
3. commit_manually()
告訴Django我們將自己控制函數中的交易處理。並且要注意,如果在視圖函數中改變了資料庫的資料並且沒有調用commit()或rollback(),那麼將拋出TransactionManagementError異常。
from django.db import transaction@transaction.commit_manuallydef viewfunc(request):...# You can commit/rollback however and whenever you wanttransaction.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_successdef AddInStockBill(request):
1.4. 事務測試
現在我們重新執行前面事務失敗的例子,來看系統啟動並執行結果是否滿足事務的基本要求。
提交後系統報錯,這我們也會探索資料庫intentory表,提交的資料復原了,庫存數量沒有更新。入庫單業務實現了正確的業務事務,避免錯誤、混亂的資料提交到資料庫中。
1.5. 小結
本章節我們用入庫的業務例子來闡述如何運行Django的事務機制,以滿足業務事務的要求,例子中我們採用了commit_on_success(),在實際應用中可以根據自己的商務邏輯採用不同的事務標識。
下一個章我們將繼續以入庫單的例子參數如何編寫支援單元測試的代碼例子。
Python開發入門與實戰10-事務