連線池¶
當建立連線時,通常意味著開啟一個 TCP 連線或 Socket。該 Socket 一次只會處理一個敘述。如果程式需要同時執行多個查詢,或者如果它處理旨在使用資料庫的並行請求,則它需要多個活動連線。
由於資料庫是與使用它們的應用程式分開的服務,因此連線可能會中斷、服務可能會重新啟動,以及其他程式可能不想關心的事情。
為了解決這個問題,通常使用連線池是一個很好的解決方案。
當使用 crystal-db
開啟資料庫時,已經有一個連線池在運作。DB.open
會回傳一個 DB::Database
物件,該物件會管理整個連線池,而不僅僅是一個單一連線。
DB.open("mysql://root@localhost/test") do |db|
# db is a DB::Database
end
當使用 db.query
、db.exec
、db.scalar
等執行敘述時,演算法如下:
- 在池中尋找可用的連線。
- 如果需要且可能,則建立一個連線。
- 如果不允許池建立新的連線,請等待連線變為可用。
- 但是,如果等待時間過長,則應中止此等待。
- 從池中取出該連線。
- 執行 SQL 指令。
- 如果沒有產生
DB::ResultSet
,則將連線返回到池中。否則,當 ResultSet 關閉時,連線將返回到池中。 - 回傳敘述結果。
如果無法建立連線,或者在執行敘述時發生連線遺失,則會重複上述過程。
只有在透過
DB::Database
發送敘述時,才會發生重試邏輯。如果透過DB::Connection
或DB::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 列印目前時間,但是如果連線遺失或整個伺服器當機幾秒鐘,程式仍將在不引發例外的情況下執行。
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