跳至內容

連線池

當建立連線時,通常意味著開啟一個 TCP 連線或 Socket。該 Socket 一次只會處理一個敘述。如果程式需要同時執行多個查詢,或者如果它處理旨在使用資料庫的並行請求,則它需要多個活動連線。

由於資料庫是與使用它們的應用程式分開的服務,因此連線可能會中斷、服務可能會重新啟動,以及其他程式可能不想關心的事情。

為了解決這個問題,通常使用連線池是一個很好的解決方案。

當使用 crystal-db 開啟資料庫時,已經有一個連線池在運作。DB.open 會回傳一個 DB::Database 物件,該物件會管理整個連線池,而不僅僅是一個單一連線。

DB.open("mysql://root@localhost/test") do |db|
  # db is a DB::Database
end

當使用 db.querydb.execdb.scalar 等執行敘述時,演算法如下:

  1. 在池中尋找可用的連線。
    1. 如果需要且可能,則建立一個連線。
    2. 如果不允許池建立新的連線,請等待連線變為可用。
      1. 但是,如果等待時間過長,則應中止此等待。
  2. 從池中取出該連線。
  3. 執行 SQL 指令。
  4. 如果沒有產生 DB::ResultSet,則將連線返回到池中。否則,當 ResultSet 關閉時,連線將返回到池中。
  5. 回傳敘述結果。

如果無法建立連線,或者在執行敘述時發生連線遺失,則會重複上述過程。

只有在透過 DB::Database 發送敘述時,才會發生重試邏輯。如果透過 DB::ConnectionDB::Transaction 發送,則不會執行重試,因為程式碼會說明預期使用特定的連線物件。

設定

可以從連線 URI 中以查詢字串形式出現的一組參數來配置池的行為。

名稱 預設值
initial_pool_size 1
max_pool_size 0 (無限制)
max_idle_pool_size 1
checkout_timeout 5.0 (秒)
retry_attempts 1
retry_delay 1.0 (秒)

當開啟 DB::Database 時,將會建立 initial_pool_size 個連線的初始數量。池永遠不會容納超過 max_pool_size 個連線。當將連線返回/釋放到池中時,如果已經有 max_idle_pool_size 個閒置連線,則會關閉該連線。

如果已達到 max_pool_size 且需要連線,請等待最多 checkout_timeout 秒,以便現有連線變為可用。

如果連線遺失或無法建立,最多重試 retry_attempts 次,每次重試之間等待 retry_delay 秒。

範例

以下程式將從 MySQL 列印目前時間,但是如果連線遺失或整個伺服器當機幾秒鐘,程式仍將在不引發例外的情況下執行。

sample.cr
require "mysql"

DB.open "mysql://root@localhost?retry_attempts=8&retry_delay=3" do |db|
  loop do
    pp db.scalar("SELECT NOW()")
    sleep 0.5
  end
end
$ crystal sample.cr
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:57
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:57
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:58
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:58
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:59
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:59
# stop mysql server for some seconds
db.scalar("SELECT NOW()") # => 2016-12-16 16:37:06
db.scalar("SELECT NOW()") # => 2016-12-16 16:37:06
db.scalar("SELECT NOW()") # => 2016-12-16 16:37:07