繼上一篇《Mysql事務探索及其在Django中的實踐(一)》交代完問題的背景和Mysql事務基礎後,這一篇主要想介紹一下事務在Django中的使用以及實際應用給咱們帶來的效率提高。sql
首先貼上Django官方文檔中關於Database Transaction一章的介紹:https://docs.djangoproject.com/en/1.9/topics/db/transactions/。數據庫
在Django中實現事務主要有兩種方式:第一種是基於django ORM框架的事務處理,第二種是基於原生地執行SQL語句的transaction處理。django
基於django ORM框架的事務處理緩存
默認狀況下,django的事務處理是自動提交(auto-commit),即在執行model.save(),model.delete()時,全部的改動會被當即提交,至關於數據庫設置了auto commit,沒有任何隱藏的rollback。安全
在網上查了一些資料,瞭解到django手動配置事務的方式主要有三種:第一種是將一個http request的全部數據庫操做包裹在一個transaction中,第二種是經過transaction中間件對http請求的事務攔截,第三種是本身在view中經過裝飾器靈活控制事務(咱們的平臺最後用的就是這一種)。框架
1.將一個http request的全部數據庫操做包裹在一個transaction中less
這種方式配置很是簡單,只須要在settings.py中的database配置中加入‘ATOMIC_REQUESTS’: True便可。如圖1所示:性能
圖1 Database中加入'ATOMIC_REQUESTS':True優化
經過這種配置,django在調每一個view方法以前會開始一個事務,當且僅當該響應沒有任何問題,django纔會提交這個事務;若是view中出現了異常,則django會回滾該事務。這樣作的好處顯而易見,就是安全簡便,可是隨着網站的流量變大,若是每一個view被調用時都打開一個事務就會變得有點繁重,從而會下降網站的效率。它對性能的影響取決於你的應用的查詢效率以及你的數據庫處理鎖的能力。此外,使用這種方式,回退的只是數據庫的狀態,而不包括其餘非數據庫項的操做,例如發送email等。網站
2.經過transaction中間件對http請求的事務攔截
配置方法是在settings.py中配置MIDDLEWARE_CLASSES,如圖2所示:
圖2 transaction中間件配置
須要注意的是,這樣配置以後,與你中間件的配置順序是有很大關係的。在 TransactionMiddleware 以後的全部中間件都會受到事務的控制。但CacheMiddleware,UpdateCacheMiddleware,FetchFromCacheMiddleware 這些中間件不會受到影響,由於cache機制有本身的處理方式,用了內部的connection來處理。另外TransactionMiddleware 只對默認的數據庫配置有效,若是要對另外的數據鏈接用這種方式,必須本身實現中間件。(此處必須聲明,對於這種方法,本人沒有仔細研究過,只是借鑑了一下網上的資料)
3.本身在view中經過裝飾器靈活控制事務
最後種方式,經過裝飾器靈活配置,也是咱們平臺最後採用的方式。
1)@transaction.autocommit
django默認的事務處理,採用此裝飾模式這種方式能夠忽視全局的transaction配置。
2)@transaction.commit_on_success
採用此裝飾模式,當此方法的全部工做完成後,纔會提交事務。
3)@transaction.commit_manually
採用這種方式,你能夠自由地隨意提交或回滾事務,徹底本身處理。若是沒有調用commit()或rollback(),則程序會拋出TransactionManagementError異常。
基於原生地執行SQL語句的transaction處理
再來說講Django中第二種事務處理方式,即用原生地執行SQL語句的方式。
圖3 原生地執行SQL語句中的事務處理
這種處理方式比較簡單,以圖3中的方法爲例,首先定義了一個遊標cursor,經過cursor任意地執行sql語句,最後經過transaction.commit_unless_managed()來提交事務。
延伸
上面介紹了Django中的兩種事務處理的方式,到這裏我忽然想到一個問題:
若是一個方法中既包含了裝飾器@transaction.commit_on_success,又執行了原生SQL語句的事務提交,當方法出現異常致使事務回滾時,原生的SQL語句所提交的事務會不會被回滾?
爲了驗證這個問題,我用Flask寫了兩個接口來進行驗證:
接口delete_and_insert和接口delete_and_insert2都是先經過cursor事務提交執行清除表,而後往表裏循環插入數據,當知足條件i=480的時候,拋出一個ValueError。惟一的區別就是接口1中採用了裝飾器@transaction.commit_on_success,而接口2中沒有。實際執行發現:
1.在調接口1時,原數據庫表中的數據不會變化,說明經過cursor執行清除表的操做也會回滾。
2.在調接口2時,原數據庫表中的數據被刪除,數據庫留下的是新的數據:全部的name都爲user,而age從1到480。
從而證實,當view方法加了裝飾器@transaction.commit_on_success後,即便view中使用了cursor執行原生sql語句,並執行了transaction.commit_unless_managed(),可是若是view中有異常拋出,整個view方法的內容都會回滾。
實際應用
最後,迴歸最初的問題自己,當我把事務應用到咱們平臺的後臺接口中後,發現了一個意外的驚喜:接口A的執行時間從原來的5-10分鐘一會兒縮短到了幾秒鐘就完成了。欣喜之餘仔細思考了一下才以爲性能顯著提高的緣由應該是:在應用事務以前,全部SQL語句都是自動提交的,每插入一條數據,數據庫表的索引可能就須要重建一次,當大量的sql語句逐一插入時,數據庫表的索引就須要不斷地重建,其中就須要耗費大量的時間。而在應用事務以後,全部的插入是一次性提交,數據庫表的索引只須要重建一次,大大減小了開銷。
這也驗證了數據庫的索引不是萬能的,合理的創建索引確實能大大地優化查詢速度,由於索引的存儲結構就像一本字典同樣,咱們在查找某個特定的字時會根據拼音的首字母的方式先找到該字的第一個字母所在頁數,而後直接跳到那一頁日後去翻。然而這也決定了字典在初始存儲這些字時就須要根據這些字的特色將每一個字放在特定的存儲位置。當有新的字加入時,爲了插入到特定的位置,就必須從新創建映射關係。
補充:合理創建索引
下面是我工做中搜集的一些關於索引創建的規則,也歡迎你們參考,指正: