問題
數據庫鏈接丟失異常
django.db.utils.OperationalError: (2013, 'Lost connection to MySQL server during query')
查詢mysql全局變量SHOW GLOBAL VARIABLES;
可以看到wait_timeout
,此變量表示連接空閑時間,MySQL默認的時間是8小時
。如果客戶端使用一個連接查詢多次數據庫,如果連續查詢則沒有問題,如果查詢幾次后停頓超過wait_timeout
后再次查詢就會出現數據庫連接丟失
Django中的數據庫連接
Django程序接受到請求之后,在第一訪問數據庫的時候會創建一個數據庫連接,直到請求結束,關閉連接。下次請求也是如此。因此,這種情況下,隨着訪問的並發數越來越高,就會產生大量的數據庫連接。即,沒收到一次請求,django就會建立一個數據庫連接
解決方案:
方案一:(不一定能解決)
使用CONN_MAX_AGE減少數據庫請求
每次請求都會創建新的數據庫連接,這對於高訪問量的應用來說完全是不可接受的。因此在Django1.6時,提供了持久的數據庫連接,通過DATABASE
配置上添加CONN_MAX_AGE
來控制每個連接的最大存活時間。具體使用可以參考最后的鏈接。
原理:
在每次創建完數據庫連接之后,把連接放到一個Theard.local的實例中。在request請求開始結束的時候,打算關閉連接時會判斷是否超過CONN_MAX_AGE
設置這個有效期。這是關閉。每次進行數據庫請求的時候其實只是判斷local中有沒有已存在的連接,有則復用。
Django中對於CONN_MAX_AGE的使用是有些限制的,使用不當,會事得其反。因為保存的連接是基於線程局部變量的,因此如果你部署方式采用多線程,必須要注意保證你的最大線程數不會多余數據庫能支持的最大連接數。如果使用開發模式運行程序(直接runserver的方式),建議不要設置CONN_MAX_AGE,因為這種情況下,每次請求都會創建一個Thread。同時如果你設置了CONN_MAX_AGE,將會導致你創建大量的不可復用的持久的連接。
CONN_MAX_AGE設置多久
CONN_MAX_AGE的時間怎么設置主要取決於數據庫對空閑連接的管理,比如你的MySQL設置了空閑1分鍾就關閉連接,那你的CONN_MAX_AGE就不能大於一分鍾。
具體解決
# django項目中 settings.py
DATABASES = {
"default": {
'ENGINE': 'django.db.backends.mysql',
'NAME': '',
'USER': '',
'PASSWORD': '',
'HOST': '',
'CONN_MAX_AGE': 9*60 # 比wait_timeout小一些
}
}
方案二:
django關閉連接流程
對django源碼中CONN_MAX_AGE
對django關閉失效連接的方法django.db.close_old_connections()
:
# Register an event to reset transaction state and close connections past
# their lifetime.
def close_old_connections(**kwargs):
for conn in connections.all():
conn.close_if_unusable_or_obsolete()
signals.request_started.connect(close_old_connections) # 一個請求開始時觸發此事件
signals.request_finished.connect(close_old_connections) # 一個請求結束時觸發此事件
通過signal
實現特定事件時執行此方法(類似於 觸發器),兩個特定事件是請求開始和請求結束。
如果報錯的是在一次請求中,所以此法通常無效,僅僅是實現每個請求關閉並重新建立連接。
解決原理:
在每一次查詢操作前,通過調用close_old_connections()
主動關閉閑置連接,主動關閉閑置連接,當再次操作數據庫,會自動建立新的連接。
-
一般情況不會出現此類問題,因為一個請求中不間斷進行數據庫查詢,無需每個請求調用此方法。
-
有時候一個請求中數據量較大,會查詢數據庫后進行一段時間其他(不涉及數據庫)處理,比如先查詢一些數據,然后進行超過MySQL的
wait_timeout
的長時間的操作。經過非常長時間,那么請求時建立的連接conn_1
已經被MySQL斷開,而在項目中在經過長時間的操作后卻仍然使用conn_1
,就會拋出異常,應該調用django.db.close_old_connections
關閉無效連接,重新連接MySQL,在操作數據庫,防止連接丟失
具體方法
from django.db import close_old_connections
def func(request):
models.User.objects.filter(name='xxx')
time.sleep(9*60*60) # 模擬執行長時間耗時操作,時間超過MySQL的 wait_timeout
close_old_connections() # 先手動關閉無效時間
models.User.objects.filter(name='xxx') # 再進行查詢