最近我們的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