09 Django之orm中的鎖和事務

---恢復內容開始---html

一 鎖

  行級鎖 python

    select_for_update(nowait=False,skip_locked=False)  #注意必須用在事務裏面,至於如何開啓事務,日後看mysql

    返回一個鎖住行知道事務結束的查詢集,若是數據庫支持,它將生成一個select ... for update語句.sql

    舉個例子:數據庫

entries = 
Entry.objects.select_for_update().filter(author=request.user)
#加互斥鎖,因爲mysql在查詢時自動加的是共享鎖,因此咱們能夠手動加上互斥鎖.
create,update,delete操做時,mysql自動加行集互斥鎖

  全部的匹配行將被鎖定,直到事務結束.這意味着能夠經過鎖防止數據被其餘事務修改.django

  通常狀況下若是其餘事物鎖定了相關行,那麼本查詢將被阻塞,直到鎖被釋放.若是這不想要使查詢阻塞的話,使用select_for_update(nowait=True).若是其餘事物持有衝突的鎖,互斥鎖,那麼查詢將引起DatabaseError異常.你也可使用select_for_update(skip_locked=True)忽略鎖定的行. nowait和skip_locked是互斥的,同時設置會致使ValueError.後端

  目前,postgresql,oracle和mysql數據庫後端支持select_for_update().可是,Mysql不支持nowait和skip_locked參數.安全

  使用不支持這些選項的數據庫後端(如MySQL)將nowait=True或skip_locked = True轉換爲select_for_update() 將致使拋出DatabaseError異常,這能夠防止代碼意外終止.oracle

二 事務

  django1.8版本以前是有不少種添加事物的方式的,中間件形式(全局的).函數裝飾器的形式,上下文管理器的形式等,可是不少方法都在1.8版本以後就更新了.app

  

1 全局開啓

    在Web應用中,經常使用的事務處理方式是將每一個請求都包裹在一個事務中。這個功能使用起來很是簡單,你只須要將它的配置項ATOMIC_REQUESTS設置爲True。

    它是這樣工做的:當有請求過來時,Django會在調用視圖方法前開啓一個事務。若是請求卻正確處理並正確返回告終果,Django就會提交該事務。不然,Django會回滾該事務。

複製代碼
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mxshop',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'root',
        'PASSWORD': '123',
        'OPTIONS': {
            "init_command": "SET default_storage_engine='INNODB'",
       #'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", #配置開啓嚴格sql模式


        }
        "ATOMIC_REQUESTS": True, #全局開啓事務,綁定的是http請求響應整個過程
"AUTOCOMMIT":False, #全局取消自動提交,慎用 },
  'other':{
    'ENGINE': 'django.db.backends.mysql', 
            ......
  } #還能夠配置其餘數據庫
}
複製代碼

    上面這種方式是統一個http請求對應的全部sql都放在一個事務中執行(要麼全部都成功,要麼全部都失敗)。是全局性的配置, 若是要對某個http請求放水(而後自定義事務),能夠用non_atomic_requests修飾器,那麼他就不受事務的管控了

複製代碼
from django.db import transaction

@transaction.non_atomic_requests
def my_view(request):
    do_stuff()

@transaction.non_atomic_requests(using='other')
def my_other_view(request):
    do_stuff_on_the_other_database()
複製代碼

    可是Django 文檔中說,不推薦這麼作。由於若是將事務跟 HTTP 請求綁定到一塊兒的時,然而view 是依賴於應用程序對數據庫的查詢語句效率和數據庫當前的鎖競爭狀況。當流量上來的時候,性能會有影響,知道一下就好了

    因此推薦用下面這種方式,經過 transaction.atomic 來更加明確的控制事務。atomic容許咱們在執行代碼塊時,在數據庫層面提供原子性保證。 若是代碼塊成功完成, 相應的變化會被提交到數據庫進行commit;若是執行期間遇到異常,則會將該段代碼所涉及的全部更改回滾。

  2 局部使用事務

    atomic(using=None, savepoint=True)[source]  ,參數:using='other',就是當你操做其餘數據庫的時候,這個事務才生效,看上面咱們的數據庫配置,除了default,還有一個other,默認的是default。savepoint的意思是開啓事務保存點,推薦看一下我數據庫博客裏面的事務部分關於保存點的解釋。

原子性是數據庫事務的一個屬性。使用atomic,咱們就能夠建立一個具有原子性的代碼塊。一旦代碼塊正常運行完畢,全部的修改會被提交到數據庫。反之,若是有異常,更改會被回滾。

    被atomic管理起來的代碼塊還能夠內嵌到方法中。這樣的話,即使內部代碼塊正常運行,若是外部代碼塊拋出異常的話,它也沒有辦法把它的修改提交到數據庫中。

    用法1:給函數作裝飾器來使用 

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

 

    用法2:做爲上下文管理器來使用,其實就是設置事務的保存點

複製代碼
from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():   #保存點
        # This code executes inside a transaction.
        do_more_stuff()

    do_other_stuff()
複製代碼

      一旦把atomic代碼塊放到try/except中,完整性錯誤就會被天然的處理掉了,好比下面這個例子:

複製代碼
from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
    except IntegrityError:
        handle_exception()

    add_children()
複製代碼

    用法3:還能夠嵌套使用,函數的事務嵌套上下文管理器的事務,上下文管理器的事務嵌套上下文管理器的事務等。下面的是函數嵌套上下文的例子:

複製代碼
from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
       #other_task() #還要注意一點,若是你在事務裏面寫了別的操做,只有這些操做所有完成以後,事務纔會commit,也就是說,若是你這個任務是查詢上面更改的數據表裏面的數據,那麼看到的仍是事務提交以前的數據。 except IntegrityError: handle_exception() add_children()
複製代碼

      這個例子中,即便generate_relationships()中的代碼打破了數據完整性約束,你仍然能夠在add_children()中執行數據庫操做,而且create_parent()產生的更改也有效。須要注意的是,在調用handle_exception()以前,generate_relationships()中的修改就已經被安全的回滾了。所以,若是有須要,你照樣能夠在異常處理函數中操做數據庫。

複製代碼
儘可能不要在atomic代碼塊中捕獲異常

  由於當atomic塊中的代碼執行完的時候,Django會根據代碼正常運行來執行相應的提交或者回滾操做。若是在atomic代碼塊裏面捕捉並處理了異常,就有可能隱蓋代碼自己的錯誤,從而可能會有一些意料以外的不愉快事情發生。

  擔憂主要集中在DatabaseError和它的子類(如IntegrityError)。若是這種異常真的發生了,事務就會被破壞掉,而Django會在代碼運行完後執行回滾操做。若是你試圖在回滾前執行一些數據庫操做,Django會拋出TransactionManagementError。一般你會在一個ORM相關的信號處理器拋出異常時遇到這個行爲。

捕獲異常的正確方式正如上面atomic代碼塊所示。若是有必要,添加額外的atomic代碼塊來作這件事情,也就是事務嵌套。這麼作的好處是:當異常發生時,它能明確地告訴你那些操做須要回滾,而那些是不須要的。

複製代碼

    爲了保證原子性,atomic還禁止了一些API。像試圖提交、回滾事務,以及改變數據庫鏈接的自動提交狀態這些操做,在atomic代碼塊中都是不予許的,不然就會拋出異常。

  下面是Django的事務管理代碼:

  • 進入最外層atomic代碼塊時開啓一個事務;
  • 進入內部atomic代碼塊時建立保存點;
  • 退出內部atomic時釋放或回滾事務;注意若是有嵌套,內層的事務也是不會提交的,能夠釋放(正常結束)或者回滾
  • 退出最外層atomic代碼塊時提交或者回滾事務;

     你能夠將保存點參數設置成False來禁止內部代碼塊建立保存點。若是發生了異常,Django在退出第一個父塊的時候執行回滾,若是存在保存點,將回滾到這個保存點的位置,不然就是回滾到最外層的代碼塊。外層事務仍然可以保證原子性。然而,這個選項應該僅僅用於保存點開銷較大的時候。畢竟它有個缺點:會破壞上文描述的錯誤處理機制。

   注意:transaction只對數據庫層的操做進行事務管理,不能理解爲python操做的事務管理

複製代碼
def example_view(request):
    tag = False
    with transaction.atomic():
        tag = True
        change_obj() # 修改對象變量
        obj.save()
        raise DataError
    print("tag = ",tag) #結果是True,也就是說在事務中的python變量賦值,即使是事務回滾了,這個賦值也是成功的
複製代碼

  還要注意:若是你配置了全局的事務,它和局部事務可能會產生衝突,你可能會發現你局部的事務完成以後,若是你的函數裏面其餘的sql除了問題,也就是沒在這個上下文管理器的局部事務包裹範圍內的函數裏面的其餘的sql出現了問題,你的局部事務也是提交不上的,由於全局會回滾這個請求和響應所涉及到的全部的sql,因此仍是建議之後的項目儘可能不要配置全局的事務,經過局部事務來搞定,固然了,看大家的業務場景。

   transaction的其餘方法

複製代碼
@transaction.atomic
def viewfunc(request):

  a.save()
  # open transaction now contains a.save()
  sid = transaction.savepoint()  #建立保存點

  b.save()
  # open transaction now contains a.save() and b.save()

  if want_to_keep_b:
      transaction.savepoint_commit(sid) #提交保存點
      # open transaction still contains a.save() and b.save()
  else:
      transaction.savepoint_rollback(sid)  #回滾保存點
      # open transaction now contains only a.save()

  transaction.commit() #手動提交事務,默認是自動提交的,也就是說若是你沒有設置取消自動提交,那麼這句話不用寫,若是你配置了那個AUTOCOMMIT=False,那麼就須要本身手動進行提交。
複製代碼

 

   爲保證事務的隔離性,咱們還能夠結合上面的鎖來實現,也就是說在事務裏面的查詢語句,我們使用select_for_update顯示的加鎖方式來保證隔離性,事務結束後纔會釋放這個鎖,例如:(瞭解)

複製代碼
@transaction.atomic ## 輕鬆開啓事務
def handle(self):
    ## 測試是否存在此用戶
    try:
        ## 鎖定被查詢行直到事務結束
        user = 
    User.objects.select_for_update().get(open_id=self.user.open_id)
        #other sql 語句
    except User.DoesNotExist:
        raise BaseError(-1, 'User does not exist.')
複製代碼

 

   經過Django外部的python腳原本測試一下事務:

複製代碼
import os

if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings")
    import django
    django.setup()

    import datetime
    from app01 import models

    try:
        from django.db import transaction
        with transaction.atomic():
            new_publisher = models.Publisher.objects.create(name="火星出版社")
            models.Book.objects.create(title="橘子物語", publish_date=datetime.date.today(), publisher_id=10)  # 指定一個不存在的出版社id
    except Exception as e:
        print(str(e))
複製代碼

 

   下面再說一些設置事務的小原則吧:

    1.保持事務短小 
    2.儘可能避免事務中rollback 
    3.儘可能避免savepoint 
    4.默認狀況下,依賴於悲觀鎖 
    5.爲吞吐量要求苛刻的事務考慮樂觀鎖 
    6.顯示聲明打開事務 
    7.鎖的行越少越好,鎖的時間越短越好

 

 

  

 

---恢復內容結束---

相關文章
相關標籤/搜索