Django (2006, 'MySQL server has gone away') 本地重現與解決
最近我們的Django專案供Java Sofa應用進行tr呼叫時, 經常會出現一個異常: django.db.utils.OperationalError: (2006, 'MySQL server has gone away')
. 本文記錄了分析, 本地重現與解決此問題的全過程.
原因分析:
Django在1.6引入長連結(Persistent connections)的概念, 可以在一個HTTP請求中一直用同一個連線對資料庫進行讀寫操作.
但我們的應用對資料庫的操作太不頻繁了, 兩次操作資料庫的間隔大於MySQL配置的超時時間(預設為8個小時), 導致下一次操作資料庫時的connection過期失效.
Our databases have a 300-second (5-minute) timeout on inactive connections. That means, if you open a connection to the database, and then you don’t do anything with it for 5 minutes, then the server will disconnect, and the next time you try to execute a query, it will fail.
重現問題:
設定mysql wait_timeout
為10s
在macOS上的mysql配置檔案路徑: /usr/local/etc/my.cnf
1 |
# Default Homebrew MySQL server config |
重啟mysql:
1 |
➜ ~ brew services restart mysql |
檢查wait_timeout
的值是否已被更新.
1 |
mysql> show variables like '%wait_timeout%'; |
重現exception:
1 |
|
有意思的一個點是, sleep 10s 之後, 第一次操作資料庫, 會出現(2013, 'Lost connection to MySQL server during query’)
異常. 之後再操作資料庫, 才會丟擲(2006, 'MySQL server has gone away’)
異常.
解決問題:
第一個最暴力的方法就是增加mysql的wait_timeout
讓mysql不要太快放棄連線. 感覺不太靠譜, 因為不能杜絕這種Exception的發生.
第二個辦法就是手動把connection直接關閉:
1 |
|
發現不會出現(2006, 'MySQL server has gone away’)
異常了, 但總感覺還是不夠優雅.
最終決定在客戶端(Django), 設定超時時間(CONN_MAX_AGE: 5
)比mysql服務端(wait_timeout = 10
)小:
1 |
DATABASES = { |
但很奇怪沒有生效??? 看了原始碼, 發現只有在request_started
(HTTP request)和request_finished
的時候, 在close_if_unusable_or_obsolete
才用到CONN_MAX_AGE
並去驗證時間關閉connection.
具體程式碼見: python3.6/site-packages/django/db/__init__.py#64
1 |
# Register an event to reset transaction state and close connections past |
而我的程式碼是處理一個任務而不是HTTP請求, 所以不會觸發這個signal. 於是我寫了一個裝飾器, 在任務的開始和結束的時候, 關閉所有資料庫連線.
1 |
from django.db import connections |
ps. CONN_MAX_AGE預設其實為0, 意味著預設在http請求和結束時會關閉所有資料庫連線.
其他:
django.db中connection和connections的區別???
connection
對應的是預設資料庫的連線, 用程式碼表示就是connections[DEFAULT_DB_ALIAS]
connections
對應的是setting.DATABASES中所有資料庫的connection