SQLAlchemy長時間未請求,數據庫連接斷開的原因、解決方案


這個問題好像和長時間連接數據庫但不打開網頁時,報錯lost connection是一個原因

問題:

寫了一個基於apscheduler的定時任務,里面的任務使用了sqlalchemy,大致如下:

scheduler.py

# ...
# 此處省略 import

# 示例任務1
def example_job1():
    exalple_query()
    
# 示例任務2
def example_job2():
    exalple_query()

# 2050-01-01前,每天定時運行

sched.add_job(example_job1, 'cron', hour=19, minute=10, second=00, end_date='2050-01-01')
sched.add_job(example_job2, 'cron', hour=20, minute=00, second=30, end_date='2050-01-01')
# sched.add_job(job_function, 'interval', seconds=10)

sched.start()

使用python scheduler.py跑起來之后,每次都是:

  • 當天可以正常運行兩個任務;
  • 第二天跑的時候,第一個任務不能正常運行(在某條query語句上卡住,沒有任何輸出),但第二個任務可以正常運行。
  • 第三天仍然是,第一
  • 個任務不能正常運行,但第二個任務可以正常運行。

原因猜想:

  1. 數據庫死鎖,導致一直等待?每天到點就鎖表,不是很能說得通,也手工排除了一下這種可能:在第二天自動跑的時候,用show processlist一直監視數據庫進程,沒有看到鎖表情況。
  2. 上一次運行過程中的某一個 session 沒 commit ?檢查代碼,沒有發現遺漏。
  3. 當進行過一輪增/刪/改/查操作后,不使用 sqlalchemy 的時候,session 需要 close?(未測試,代碼中沒有使用 close,只是用了 commit,所以有可能是這個原因)
  4. SQLAlchemy長時間未請求數據庫連接斷開?(可能性較大)

解決方案:

分析了一下,覺得SQLAlchemy長時間未請求數據庫連接斷開的可能性較大。

臨時解決方案:在正式運行定時任務之前,先跑一個session.query(),相當於喚醒連接。

修改后如下:

# ...
# 此處省略 import

# 保持連接
def keep_sqlalchemy_connected_job():
    result_no_use = session.query(Example_table).first()

# 示例任務1
def example_job1():
    exalple_query()
    
# 示例任務2
def example_job2():
    exalple_query()

# 2050-01-01前,每天定時運行
sched.add_job(keep_sqlalchemy_connected_job, 'cron', hour=18, minute=55, second=00, end_date='2050-01-01')  # 保持連接
sched.add_job(example_job1, 'cron', hour=19, minute=10, second=00, end_date='2050-01-01')
sched.add_job(example_job2, 'cron', hour=20, minute=00, second=30, end_date='2050-01-01')
# sched.add_job(job_function, 'interval', seconds=10)

sched.start()

拓展閱讀:SQLAlchemy長時間未請求數據庫連接斷開

當較長時間沒有去訪問網站,再次打開時就會報一個數據庫連接失敗的錯誤。這也解釋了之前遇到過的lost connection問題。

連接池連接mysql數據庫失敗,應該是mysql數據庫連接超時,mysql數據庫配置文件存在以下兩個參數,是負責管理連接超時的。

  1. interactive_timeout:針對交互式連接
  2. wait_timeout:針對非交互式連接。

所謂的交互式連接,即在mysql_real_connect()函數中使用了CLIENT_INTERACTIVE選項。說得直白一點,通過mysql客戶端連接數據庫是交互式連接,通過jdbc連接數據庫是非交互式連接。

這兩個參數默認都是28800秒,即8小時,也就是超過8小時的連接就會自動失效。這本身並沒什么問題,真正的問題是:我們做項目一般使用數據庫連接池來獲取連接,連接池里的連接可能會較長時間不關閉,等待被使用,這就與mysql連接超時機制起沖突了,當連接池配置永不關閉或者關閉時間超過8小時就會出現我所遇到的問題。

當超過8個小時沒有新的數據庫請求的時候,數據庫連接就會斷開,如果我們連接池的配置是用不關閉或者關閉時間超過8小時,這個時候連接池沒有回收並且還認為連接池與數據庫之間的連接還存在,就會繼續連接,但是數據庫連接斷開了,就會報錯數據庫連接失敗!

解決辦法:

  1. 修改mysql配置文件里wait_timeout參數,讓這個時間大於連接池的回收時間(修改配置文件需要重啟數據庫,不推薦!)
  2. 修改數據庫連接池的配置,數據庫連接池都會帶有一個參數:回收時間(就是一定時間內不使用就會回收),修改這個參數的值,不要大於wait_timeout的值即可。在flask-SQLAlchemy中有個配置是SQLALCHEMY_POOL_RECYCLE(多之后對線程池中的線程進行一次連接的回收),如果這個值是-1代表永不回收,Flask-SQLALchemy 自動設定這個值為2小時,我們可以將這個值設置的小於wait_timeout參數的值也就是8小時即可。
  3. 禁用SQLAlchemy提供的數據庫連接池
    只需要在調用 create_engine 是指定連接池為 NullPool,SQLAlchemy就會在執行 session.close() 后立刻斷開數據庫連接。當然,如果 session 對象被析構但是沒有被調用 session.close(),則數據庫連接不會被斷開,直到程序終止。

問題很明顯了。如果不調用close 方法
當前連接不會釋放回空閑連接池中,一直存在於 活動連接池中,當空閑連接池耗光之后 pop 會從 活動連接池中選取第一個連接,
返回給應用.而這個時候 這個連接從最后次使用到拿出來可能過了好幾分鍾了。就真的是個處於關閉狀態的連接了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM