連線池透過重複使用資料庫連線,而非為每個請求建立新的連線,來提升應用程式效能。 開啟連線包含多個耗時步驟:
- 驅動程式建立網路通訊端。
- 司機完成 TLS 握手。
- 驅動程式會向伺服器進行驗證。
- 驅動程式會驗證連線參數。
連線池會讓連線保持開放並可重複使用,所以你的應用程式不需要每次請求重複這些步驟。
預設行為
當你建立第一個連線時,連線集區預設會啟用。 預設設定包括:
| Setting | 預設值 | 描述 |
|---|---|---|
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)
Parameters
該 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)
Note
在建立任何連線前,先設定好池化設定。 建立連線後再打電話 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 Database 會根據服務層級強制執行連線限制。 以下數值為近似值;請查看連結文件中的電流限制:
| 服務層級 | 並行連線數上限 |
|---|---|
| 基本 | 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)