{Django基礎六之ORM中的鎖和事務}一 鎖 二 事務
本節目錄
- 一 鎖
- 二 事務
一 鎖
行級鎖
select_for_update(nowait=False, skip_locked=False) #註意必須用在事務裏面,至於如何開啟事務,我們看下面的事務一節。
返回一個鎖住行直到事務結束的查詢集,如果數據庫支持,它將生成一個 SELECT ... FOR UPDATE 語句。
舉個例子:
entries = Entry.objects.select_for_update().filter(author=request.user) #加互斥鎖,由於mysql在查詢時自動加的是共享鎖,所以我們可以手動加上互斥鎖。create、update、delete操作時,mysql自動加行級互斥鎖
所有匹配的行將被鎖定,直到事務結束。這意味著可以通過鎖防止數據被其它事務修改。
一般情況下如果其他事務鎖定了相關行,那麽本查詢將被阻塞,直到鎖被釋放。 如果這不想要使查詢阻塞的話,使用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異常,這可以防止代碼意外終止。
表鎖(了解)
class LockingManager(models.Manager):
""" Add lock/unlock functionality to manager.
Example::
class Job(models.Model): #其實不用這麽負載,直接在orm創建表的時候,給這個表定義一個lock和unlock方法,借助django提供的connection模塊來發送鎖表的原生sql語句和解鎖的原生sql語句就可以了,不用外層的這個LckingManager(model.Manager)類
manager = LockingManager()
counter = models.IntegerField(null=True, default=0)
@staticmethod
def do_atomic_update(job_id)
‘‘‘ Updates job integer, keeping it below 5 ‘‘‘
try:
# Ensure only one HTTP request can do this update at once.
Job.objects.lock()
job = Job.object.get(id=job_id)
# If we don‘t lock the tables two simultanous
# requests might both increase the counter
# going over 5
if job.counter < 5:
job.counter += 1
job.save()
finally:
Job.objects.unlock()
"""
def lock(self):
""" Lock table.
Locks the object model table so that atomic update is possible.
Simulatenous database access request pend until the lock is unlock()‘ed.
Note: If you need to lock multiple tables, you need to do lock them
all in one SQL clause and this function is not enough. To avoid
dead lock, all tables must be locked in the same order.
See http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html
"""
cursor = connection.cursor()
table = self.model._meta.db_table
logger.debug("Locking table %s" % table)
cursor.execute("LOCK TABLES %s WRITE" % table)
row = cursor.fetchone()
return row
def unlock(self):
""" Unlock the table. """
cursor = connection.cursor()
table = self.model._meta.db_table
cursor.execute("UNLOCK TABLES")
row = cursor.fetchone()
return row
二 事務
關於MySQL的事務處理,我的mysql博客已經說的很清楚了,那麽我們來看看Django是如果做事務處理的。django1.8版本之前是有很多種添加事務的方式的,中間件的形式(全局的)、函數裝飾器的形式,上下文管理器的形式等,但是很多方法都在1.8版之後給更新了,下面我們只說最新的:
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.鎖的行越少越好,鎖的時間越短越好
{Django基礎六之ORM中的鎖和事務}一 鎖 二 事務