使用 mssql-python 实现连接池

连接池通过重复使用数据库连接,而不是为每个请求创建新的连接,从而提升应用性能。 开通连接涉及多个耗时步骤:

  • 驱动程序创建一个网络套接字。
  • 驱动程序完成了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>;...")

连接生命周期

获取(建立连接):

  1. 连接池会移除陈旧的(因空闲超时而失效的)连接。
  2. 池尝试重复利用已有的连接:
    • 它会检查连接是否处于正常状态。
    • 它会重置连接状态。
    • 如果两次检查都成功,则返回连接。
  3. 如果没有可重用连接且池低于 max_size,驱动程序会创建一个新的连接。
  4. 如果连接池已满且没有有效连接,驱动程序会引发错误。

释放(返回连接):

  1. 如果泳池有容量,它会储存连接以供重复使用。
  2. 如果连接池处于 max_size 状态,驱动程序会立即关闭连接。

连接运行状况检查

驱动程序在重用池连接前会先进行连接健康检查。

  1. 活检:确保网络连接仍然有效。
  2. 复位检查:重置会话状态(隔离级别、设置),以便干净地重用。

如果任一检查失败,池会丢弃该连接并重新创建连接。

自动清理

  • 空闲超时:驱动程序会关闭超过 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)