Django 中事務的使用
Django默認的事務行為
默認情況下,在Django中事務是自動提交的。當我們運行Django內置的模板修改函數時,例如調用model.save()或model.delete()時,事務將被立即提交。這種機制和數據庫的自動提交事務機制類似。記住這里沒有默認的回滾機制
在HTTP請求上加事務
對於Web請求,Django官方推薦使用中件間TransactionMiddleware來處理請求和響應中的事務。它的工作原理是這樣的:當一個請求到來時,Django開始一個事務,如果響應沒有出錯,Django提交這期間所有的事務,如果view中的函數拋出異常,那么Django會回滾這之間的事務。
為了實現這個特性,需要在MIDDLEWARE_CLASSES setting中添加TransactionMiddleware:
MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.transaction.TransactionMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
)
順序很重要,TransactionMiddleware中間件會將置於其后的中間件都包含在事務的范圍之中(用於緩存的中間件除外,他們不受影響,例如CacheMiddleware,UpdateCacheMiddleware和FetchFromCacheMiddleware)。
另外需要注意的是,TransactionMiddleware只會影響DATABASES設置中的默認的數據庫,對於其它的數據庫,如果我們實現事務控制的話只能用別的方案了。
在View中實現事務控制
如果想在更細粒度的條件下(例如在一個view函數中)控制事務,我們可以使用django.db.transaction。有兩種用法:
使用裝飾器
from django.db import transaction
@transaction.commit_on_success
def viewfunc(request):
# this code executes inside a transaction
使用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
這兩種方法都可以正常工作。不過如果使用的Python版本為2.5並且要使用with語法的話,還需加一句
from future import with_statement。
所以為了最大的兼容性,下面的示例使用裝飾器來實現事務。
autocommit()
使用autocommit裝飾器可以將view函數中的事務還原成Django默認的自動提交模式,無視全局事務的設置。
示例:
from django.db import transaction
@transaction.autocommit
def viewfunc(request):
@transaction.autocommit(using="my_other_database")
def viewfunc2(request):
commit_on_success()
顧名思義,view函數成功則提交事務,否則回滾。用法同上。
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()
@transaction.commit_manually(using="my_other_database")
def viewfunc2(request):
transaction事務內不執行數據庫的commit操作,除非手動commit
transaction最基本的功能。
代碼場景:
在事務當前啟動celery異步任務, 無法獲取未提交的改動.
def example_view(request):
with transaction.atomic():
change_obj() # 修改對象變量
obj.save()
async_task.delay(obj.id)
def async_task(obj_id):
obj = Model.objects.get(pk=obj_id)
read_the_obj() # 讀取對象信息
atomic提供兩種方案實現事務
在 Django 中可以通過django.db.transaction 模塊提供的atomic來定義一個事務
transaction只對數據庫層的操作進行事務管理,不能理解為python操作的事務管理
即使事務代碼塊發生了DataError,事務回滾,也僅是數據庫層面的回滾,針對python的操作依然已完成.
- 裝飾器用法:
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# 這些代碼會在一個事務中執行
......
裝飾器用法:整個視圖中所有 MySQL 數據庫的操作都看做一個事務,范圍太大,不夠靈活。而且無法直接作用於類視圖
- with 語句用法:
from django.db import transaction
def viewfunc(request):
# 這部分代碼不在事務中,會被 Django 自動提交
......
with transaction.atomic():
# 這部分代碼會在事務中執行
......
with 語句用法:可以靈活的有選擇性的把某些 MySQL 數據庫的操作看做一個事務。而且不用關心視圖的類型。
- 三句話使用
from django.db import transaction
# 創建保存點
save_id = transaction.savepoint()
# 回滾到保存點
transaction.savepoint_rollback(save_id)
# 提交從保存點到當前狀態的所有數據庫事務操作
transaction.savepoint_commit(save_id)
使用事務保存訂單數據
class OrderCommitView(LoginRequiredJSONMixin, View):
"""訂單提交"""
def post(self, request):
"""保存訂單信息和訂單商品信息"""
# 獲取當前保存訂單時需要的信息
......
# 顯式的開啟一個事務
with transaction.atomic():
# 創建事務保存點
save_id = transaction.savepoint()
# 暴力回滾
try:
# 保存訂單基本信息 OrderInfo(一)
order = OrderInfo.objects.create(
order_id=order_id,
user=user,
address=address,
total_count=0,
total_amount=Decimal('0'),
freight=Decimal('10.00'),
pay_method=pay_method,
status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] else
OrderInfo.ORDER_STATUS_ENUM['UNSEND']
)
# 從redis讀取購物車中被勾選的商品信息
redis_conn = get_redis_connection('carts')
redis_cart = redis_conn.hgetall('carts_%s' % user.id)
selected = redis_conn.smembers('selected_%s' % user.id)
carts = {}
for sku_id in selected:
carts[int(sku_id)] = int(redis_cart[sku_id])
sku_ids = carts.keys()
# 遍歷購物車中被勾選的商品信息
for sku_id in sku_ids:
# 查詢SKU信息
sku = SKU.objects.get(id=sku_id)
# 判斷SKU庫存
sku_count = carts[sku.id]
if sku_count > sku.stock:
# 出錯就回滾
transaction.savepoint_rollback(save_id)
return http.JsonResponse({
'code': RETCODE.STOCKERR,
'errmsg': '庫存不足'})
# SKU減少庫存,增加銷量
sku.stock -= sku_count
sku.sales += sku_count
sku.save()
# 修改SPU銷量
sku.goods.sales += sku_count
sku.goods.save()
# 保存訂單商品信息 OrderGoods(多)
OrderGoods.objects.create(
order=order,
sku=sku,
count=sku_count,
price=sku.price,
)
# 保存商品訂單中總價和總數量
order.total_count += sku_count
order.total_amount += (sku_count * sku.price)
# 添加郵費和保存訂單信息
order.total_amount += order.freight
order.save()
except Exception as e:
logger.error(e)
transaction.savepoint_rollback(save_id)
return http.JsonResponse({
'code': RETCODE.DBERR,
'errmsg': '下單失敗'})
# 提交訂單成功,顯式的提交一次事務
transaction.savepoint_commit(save_id)
# 清除購物車中已結算的商品
pl = redis_conn.pipeline()
pl.hdel('carts_%s' % user.id, *selected)
pl.srem('selected_%s' % user.id, *selected)
pl.execute()
# 響應提交訂單結果
return http.JsonResponse({'code': RETCODE.OK,
'errmsg': '下單成功',
'order_id': order.order_id})
發生Dataerror異常的回滾僅在數據庫層面操作,因此不可以根據model object的屬性值判斷是否正確完成了事務.
另外,雖然Django對數據庫層面以ORM完成了很具體的抽象,但應該要清楚地意識到我們操作的model object和數據庫內容本質不同,DJANGO只在查詢和提交時進行數據庫操作.