连接池通过重复使用数据库连接,而不是为每个请求创建新的连接,从而提升应用性能。 开通连接涉及多个耗时步骤:
- 驱动程序创建一个网络套接字。
- 驱动程序完成了TLS握手。
- 驱动程序通过服务器进行认证。
- 驱动程序验证连接参数。
连接池保持连接开放并可重复使用,因此你的应用无需为每个请求重复这些步骤。
默认行为
创建第一个连接时,默认会启用连接池。 默认设置为:
| 设置 | 默认值 | 描述 |
|---|---|---|
max_size |
100 | 每个唯一连接字符串的最大连接数 |
idle_timeout |
600 秒 (10 分钟) | 空闲连接关闭前的秒数。 |
import mssql_python
# Pooling is automatically enabled with defaults
conn = mssql_python.connect(connection_string)
配置连接池
在创建任何连接前先配置池化:
import mssql_python
# Configure custom pool settings
mssql_python.pooling(max_size=50, idle_timeout=300)
# Now create connections
conn = mssql_python.connect(connection_string)
参数
该 pooling() 函数接受以下参数:
| 参数 | 类型 | 默认 | 描述 |
|---|---|---|---|
max_size |
int | 100 | 每个连接字符串的最大合并连接数。 |
idle_timeout |
int | 600 | 闲置连接从连接池中被移除前的秒数。 |
enabled |
bool | 是 | 启用或关闭池化。 |
禁用连接池化
要禁用池化,请在创建连接前先调用 pooling() :enabled=False
import mssql_python
mssql_python.pooling(enabled=False)
# Connections are now created and destroyed per use
conn = mssql_python.connect(connection_string)
注释
在建立任何连接前先设置好池配置。 建立连接后再打电话 pooling() 没有效果。
池化的工作原理
连接绳隔离
每个唯一的连接字符串都有自己独立的连接池。 池之间不共享不同连接串的连接:
# These use separate pools
conn1 = mssql_python.connect("Server=<server1>;Database=<database1>;...")
conn2 = mssql_python.connect("Server=<server2>;Database=<database2>;...")
连接生命周期
获取(建立连接):
- 连接池会移除陈旧的(因空闲超时而失效的)连接。
- 池尝试重复利用已有的连接:
- 它会检查连接是否处于正常状态。
- 它会重置连接状态。
- 如果两次检查都成功,则返回连接。
- 如果没有可重用连接且池低于
max_size,驱动程序会创建一个新的连接。 - 如果连接池已满且没有有效连接,驱动程序会引发错误。
释放(返回连接):
- 如果泳池有容量,它会储存连接以供重复使用。
- 如果连接池处于
max_size状态,驱动程序会立即关闭连接。
连接运行状况检查
驱动程序在重用池连接前会先进行连接健康检查。
- 活检:确保网络连接仍然有效。
- 复位检查:重置会话状态(隔离级别、设置),以便干净地重用。
如果任一检查失败,池会丢弃该连接并重新创建连接。
自动清理
-
空闲超时:驱动程序会关闭超过
idle_timeout时长未使用的连接。 -
进程退出:当 Python 进程退出时,
atexit处理器关闭所有池连接。
最佳做法
合理调整池规模
使池大小与应用程序的并发需求相匹配。
# For a web application with 20 concurrent requests
mssql_python.pooling(max_size=25) # Slightly more than expected concurrency
使用上下文管理器
上下文管理器可确保你将连接妥善归还到连接池中。
with mssql_python.connect(connection_string) as conn:
cursor = conn.cursor()
cursor.execute("SELECT TOP 5 Name, ListPrice FROM Production.Product")
rows = cursor.fetchall()
# Connection returned to pool
保持连接线一致
连接串中不同的参数会创建独立的池。
# These create THREE separate pools (inefficient)
conn1 = mssql_python.connect("Server=<server>;Database=<database>;Encrypt=yes;")
conn2 = mssql_python.connect("SERVER=<server>;DATABASE=<database>;ENCRYPT=yes;") # Different case
conn3 = mssql_python.connect("Server=<server>;Database=<database>;Encrypt=yes;", timeout=30) # Extra parameter
# Use a constant connection string instead
CONNECTION_STRING = "Server=<server>;Database=<database>;Encrypt=yes;"
conn1 = mssql_python.connect(CONNECTION_STRING)
conn2 = mssql_python.connect(CONNECTION_STRING) # Same pool
请考虑 Azure SQL 连接限制
Azure SQL 数据库 根据服务层级强制连接限制。 以下数值为近似值;请查看链接文档以了解当前限制:
| 服务层 | 最大并发连接数 |
|---|---|
| 基本 | 30 |
| 标准 S0-S2 | 60-120 |
| 标准S3及后续版本 | 200 |
| 高级 | 500 |
将你的 max_size 价值设定在这些限制以下。
# For Azure SQL Standard S2 (120 limit)
mssql_python.pooling(max_size=100) # Leave headroom
根据你的工作量调整空闲超时
-
频繁连接:使用较大的
idle_timeout值以保持连接活跃。 -
零星连接:使用较短的
idle_timeout值以释放资源。
# High-frequency API: keep connections warm
mssql_python.pooling(idle_timeout=1800) # 30 minutes
# Batch job running every hour: release between runs
mssql_python.pooling(idle_timeout=60) # 1 minute
局限性
当前实现相比其他驱动存在一些局限性:
| Feature | 地位 |
|---|---|
ClearPool() / ClearAllPools() |
暂无 |
| 池统计信息/监控 | 暂无 |
| 每个连接的连接池覆盖设置 | 暂无 |
| 最小泳池规模 | 不可配置。 |
示例:网页应用模式
以下Flask示例展示了连接如何在不同请求间透明地池化:
import mssql_python
from flask import Flask, g
app = Flask(__name__)
# Configure pooling at startup
mssql_python.pooling(max_size=20, idle_timeout=300)
def get_db():
if 'db' not in g:
g.db = mssql_python.connect(app.config['DATABASE_URL'])
return g.db
@app.teardown_appcontext
def close_db(error):
db = g.pop('db', None)
if db is not None:
db.close() # Returns to pool
@app.route('/products')
def list_products():
conn = get_db()
cursor = conn.cursor()
cursor.execute("SELECT TOP 5 Name, ListPrice FROM Production.Product")
return cursor.fetchall()
识别泳池耗尽
当连接池中的所有连接都在使用中,而你又请求新连接时,便会出现如下症状:
- 连接在等待可用连接时会卡住或超时。
- 应用吞吐量在负载下突然下降。
- 随着驱动创建无法重用的连接,内存使用率会上升。
常见原因:
- 连接不会被归还到池中。 用完后务必关闭连接,或使用上下文管理器。 没有关闭的连接会保持关闭状态。
- 泳池规模对工作量来说太小了。 如果你有 50 个并发请求,但
max_size=20,则会有 30 个请求处于等待状态。 - 长时间运行的查询会占用连接。 拆分长操作或使用专用连接进行批量处理。
如何修复:
# 1. Always use context managers to guarantee return
with mssql_python.connect(connection_string) as conn:
cursor = conn.cursor()
cursor.execute("SELECT ...")
rows = cursor.fetchall()
# Connection returned to pool here, even if an exception occurs
# 2. Size the pool to match your concurrency
mssql_python.pooling(max_size=50) # Match or slightly exceed expected concurrent connections
# 3. Reduce idle timeout if connections go stale
mssql_python.pooling(idle_timeout=120)