shop_platform - sqlalchemy.exc.TimeoutError
發生的問題
啟動伺服器後,第二十一次請求「需要查詢資料庫」的頁面時,會出現「sqlalchemy.exc.TimeoutError: QueuePool limit of size 10 overflow 10 reached, connection timed out, timeout 30.00 (Background on this error at: https://sqlalche.me/e/14/3o7r)」的錯誤訊息。如果請求是不需要查詢資料庫的(例如靜態檔案或前往登入頁面),就不會發生此錯誤。
把 size 和 overflow 設定成更小的數字,可以更快重現這個錯誤。
感覺起來,就像查詢完資料庫以後,沒有把 connection 釋出一樣,導致「size + overflow」是多少,就只能查詢多少次資料庫。
發現瀏覽器每向伺服器請求一次「需要查詢資料庫」的頁面,在 MySQL Workbench 用 show PROCESSLIST; 指令的查詢結果就會多出一筆資料,筆數達到 size + overflow 時,若再次請求「需要查詢資料庫」的頁面,就會噴 QueuePool limit of size 10 overflow 10 reached 的錯誤。關閉伺服器時,那幾筆資料才會被刪除。
嘗試過但失敗的方法
-
@app.teardown_appcontext # 改成 @app.teardown_request 也沒用 def teardown_db(exception): db.session.remove() # 改成 db.session.close() 也沒用 print('***** session.remove') # 這一行有印出來,所以此函式有被執行
把 FLASK_ENV=development 拿掉,於是 FLASK_ENV 變成預設的 production
app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"] = True
終於成功的方法
第一種方法:用 Application Factories,這樣不管是在終端機用 flask run 指令開啟伺服器,或是按 PyCharm 上的 run 按鈕開啟伺服器,都可以避免 QueuePool limit reached。
第二種方法,若不使用 Application Factories,那麼只要在終端機用 flask run 指令開啟伺服器,而不是按 PyCharm 上的 run 按鈕開啟伺服器,就可以避免 QueuePool limit reached。
在 MySQL Workbench 用 show PROCESSLIST; 觀察 connection,發現改成用上述任一方法後,同一個 connection 就會一直被重複使用,不會再開新的 connection 了。
目前我還不太明白,為什麼這兩個方式可以解決問題。
用 flask run 啟動伺服器,不會執行 if __name__ == '__main__': 裡面的內容,按 run 按鈕才會。這似乎意味著,flask run 是在某個檔案裡面,把 app.py 檔案當作模組載入,所以__name__ == '__main__' 會是 False;而按 run 按鈕是直接執行 app.py 檔案。
在終端機用 flask run 指令開啟伺服器,顯示的 log 是
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
按 PyCharm 上的 run 按鈕開啟伺服器,顯示的 log 是
* Serving Flask app 'app' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
後來發現,flask-sqlalchemy 本來就會自己關閉 session,不需要自己再註冊一個關閉 session 的函式。建立 SQLAlchemy 這個 class 的 instance 後,一但呼叫 init_app method,就會註冊 shutdown_session 函式,request 要結束時就會呼叫 shutdown_session 函式。但在執行 shutdown_session 時,如果出現 exception,就沒辦法執行到「self.session.remove()」那一行,導致 session 沒有關閉。其中一個解決方式是自己再註冊一個關閉 session 的函式,shutdown_session 沒關到的 connection 就由新註冊的函式來關(不過這個方式感覺很不 clean)。
參考資料
最後更新日期: 2021 年 10 月 8 日
Comments
Post a Comment