sqlalchemy的QueuePool

SQLAlchemy與數據庫連接的QueuePool

1. 引入:從常見的一種連接錯誤說起

有關SQLAlchemy與數據庫的連接(Connection),最常見的一種runtime error如下所示:

QueuePool limit of size <x> overflow <y> reached, connection timed out, timeout <z>
  • 這個異常的含義是當前系統所需併發數據庫連接(對應的sqlalchemy.engine.Connection()sqlalchemy.orm.session.Session(),下文中將這二者統稱爲連接)超過了當前使用的engine所配置的併發連接數目上限(該上限由兩個值組成:pool_sizemax_overflow,這點我們將在下面討論)。

  • 簡單理解:連接池設置只讓連10個,但是同時來了11個請求,那麼另外一個就得等其他10箇中的某一個處理完後空出鏈接,纔會去處理它。並且等到了就處理,如果一直到設置的超時時間還沒有被處理,則會報超時錯誤。

下面我們總結修復程序中出現的上述錯誤時需要注意的幾點重要情況。

2. 詳解:SQLAlchemy的連接池

SQLAlchemy連接數據庫所使用的Engine對象默認採用一個連接池(a pool of connections)來管理連接

當我們使用Engine對象所對應的SQL數據庫連接的資源時,這些對數據庫的連接是通過一個連接池(Connection pooling)來管理的。當我們釋放(release)一個連接資源時,這個連接並不是被銷燬了,而是仍然連接着數據庫,只不過其將會被重新存儲如一個用於管理連接的連接池(默認爲QueuePool)中。放入連接池中的連接可以被複用。

事實上總有一定數目的數據庫連接被保存在這個連接池中,即使在我們的代碼中看起來像是連接被釋放了一樣。這些連接會在我們的程序結束運行之後自動被銷燬,或者當我們顯式地調用銷燬連接池的代碼時被銷燬。

a.連接複用

由於這個連接池的存在,每當我們在代碼中調用Engine.connect()方法或者調用ORM對應的Session的時候,往往會得到一個已存在與連接池中的數據庫連接,而不是得到了一個全新的連接對象。然而當連接池中沒有現成可用的連接對象的時候,在不超過配置所允許的連接上限的條件下,新的連接對象會被創建並返回給調用這些方法的程序。

b.默認使用的QueuePool

  • SQLAlchemy默認所使用的連接池爲sqlalchemy.pool.QueuePool。當目前總連接數沒有超過配置的上限且池中沒有現成可用的連接的情況下,一個新的連接會被建立並返回給調用創建新連接的方法的程序。這個上限等於create_engine.pool_sizecreate_engine.max_overflow之和。配置engine的代碼如下所示:
engine = create_engine("mysql://user_name:password@host/db", pool_size=x, max_overflow=y, pool_timeout=z)
  • 上文定義的engine同時允許最多x+y個併發且活躍的連接。如果在併發活躍的連接已經有x+y個時,新的對於連接的請求將會被阻塞(block),直到有一個連接對象可用爲止。阻塞(block)的時間由create_engine.pool_timeout指定,即z秒(默認情況下爲30秒)。其中create_engine.pool_size參數指定的是連接池中最多緩存的連接數目,而create_engine.max_overflow指定的是除連接池中已經緩存的連接對象之外,還允許連接池“上溢(overflow)”多少個連接對象來響應數據庫操作的請求。

  • sqlalchemy.pool.QueuePool外,我們還可用Connection Pooling中提到的其他實現作爲傳入create_engine.pool的參數。例如使用sqlalchemy.pool.NullPool可以完全禁用連接池。對這一配置的討論超出了本文的範圍,故在此不做進一步的討論。

c.可上溢的連接池

如果我們將參數create_engine.max_overflow設置爲"-1",那麼連接池會允許“上溢”無限多的新連接。在這種情況下,連接池永遠不會阻塞一個新的數據庫連接請求。相反,每當有新的連接請求且無當前可用的連接對象,連接池就會無條件地創建新的連接對象來返回給這個請求。

然而,即使我們在程序端不限制併發的數據庫連接的數目,如果程序無限制的創建新的數據庫連接對象,連接的數目最終會到達數據庫端的連接數目上限,並且耗盡所有數據庫允許的連接,最終同樣會造成程序異常。更爲需要注意的是,在這種情況下 ,在程序耗盡數據庫連接資源之前往往還會耗盡許多其他的資源,並且還很可能會影響運行在同一臺服務器上的其他依賴於數據庫訪問的程序或數據庫本身,造成其他程序異常或崩潰。

基於以上討論,我們可以知道連接池對於併發數據庫連接的數目限制可以被看做是一道保證數據庫正常運作的安全閥。在限制了連接數目的情況下,即使程序本身出現了錯誤導致不斷請求新建數據庫連接,連接池的限制仍然可以保證數據庫及其他依賴數據庫的程序的安全運行。所以如果我們收到上述的錯誤信息,最好的解決辦法是根據當前程序的需求重新定義連接池允許的數目上限,或者優化程序以減少併發數據庫連接的使用,而不是將上限設置爲無限多。

d.導致可用連接被用盡的可能原因

連接池的上限小於程序中需要併發使用連接的請求的數目

  • 這是導致連接被用盡問題最直接的一種原因。
  • 如果我們的程序使用一個大小爲20的線程池來進行併發處理且每個線程都需要一個單獨的數據庫連接,而我們定義的連接池大小隻有10,那麼顯然將會出現連接被用盡的問題。這種情況下,就應該通過增加連接池大小或減少併發線程數目的方法來解決問題。一般來說,我們應當保證連接池的大小不小於線程池的數目。

連接沒有被釋放

  • 另一個常見的導致連接用盡的原因是連接在被使用之後沒有被釋放,或說沒有被歸還給連接池。

  • 雖然當連接對象由於沒有引用而被垃圾收集之後其對應的連接資源仍將被釋放還給連接池,但由於垃圾收集的不確定性,這一機制不應當被用來作爲釋放連接資源的手段。

  • 連接沒有被釋放一般是因爲程序中沒有顯式地調用相應方法導致的。所以當我們使用完連接對象之後,應當顯式地調用連接的釋放方法。例如如果我們在使用ORM Session,則應當在合適的地方調用Session.close()方法釋放Session對象。或者當我們在使用Core的情況下在合適的位置調用.close()方法釋放Connection對象。或者我們也可以使用所謂的context manager幫助我們在使用完畢後自動調用.close()方法釋放資源(例如將代碼寫在“with”代碼塊中)。

程序試圖執行一個運行時間很長的數據庫事務(transaction)

  • 數據庫的事務是一種非常昂貴的操作,因此不應該用來閒置着等待某些事件發生。例如等待用戶點擊某個按鈕,或者等待一個長時間運行的任務返回結果。對於事務,切記不要一直維持着一個事務而不去結束。如果程序需要進行數據庫交互並且和一個事件互動,我們應該當且僅當需要的時候打開一個持續事件很短的事務,並在使用結束後就立即關閉。

當我們編程的時候,切記,如果文章開頭的錯誤信息被拋出,這往往是由於程序本身的邏輯問題導致的,而連接池僅僅是在幫助我們暴露這些問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章