Django 中事務的使用


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只在查詢和提交時進行數據庫操作.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM