连接池¶
建立连接通常意味着打开一个 TCP 连接或套接字。套接字一次处理一条语句。如果一个程序需要同时执行许多查询,或者如果它处理并发请求以使用数据库,则它将需要多个活动连接。
由于数据库是使用它们的应用程序的独立服务,因此连接可能会断开,服务可能会重新启动,以及其他程序可能不想关心的问题。
为了解决这些问题,连接池通常是一个不错的解决方案。
当使用 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