JDBC Connection Management#

JDBC (Java Database Connectivity) API 提供了一個與資料庫伺服器通訊的通用介面。所有網路邏輯與資料庫特定的通訊協定都隱藏在廠商無關的 JDBC API 背後。java.sql.Driver 是與 JDBC API 互動的主要入口點,負責定義實作版本細節並提供資料庫連線的存取。

JDBC 定義了四種驅動類型,其中 Type 4(純 Java 實作資料庫通訊協定)因為易於設定與除錯,是最常被採用的方案。

Figure 2.1: JDBC plugin architecture

DriverManager#

DriverManager 提供以下方法來取得資料庫連線:

  • getConnection(String url, Properties info)
  • getConnection(String url, String user, String password)
  • getConnection(String url)

每次呼叫 getConnection() 時,DriverManager 都會向底層 Driver 請求一個新的實體連線 (physical connection)。這個過程涉及建立 TCP socket 連線到資料庫伺服器。

Figure 2.2: DriverManager connection

JDBC 最初於 1997 年推出時,僅支援 DriverManager 來取得連線,適用於當時常見的 two-tier 架構:每個桌面應用程式實例各自擁有一條專屬的資料庫連線。

Figure 2.3: Two-tier architecture

自 JDBC 4.0 起,Service Provider Interfaces 機制可以自動發現 classpath 中所有可用的驅動程式,不再需要手動載入驅動。

DataSource#

1999 年 J2EE 與 JDBC 2.0 一同推出,標誌著 Enterprise Java 的開始。企業應用程式採用 three-tier 架構,中間層作為使用者請求與各種資料來源(如關聯式資料庫、messaging queues)之間的橋樑。

Figure 2.4: Three-tier architecture

如果說 DriverManager 是實體連線的工廠,那麼 javax.sql.DataSource 介面就是邏輯連線 (logical connection) 的提供者:

  • Application server 提供的是邏輯連線(proxy 或 handle),而非實體連線
  • 這使得中間層可以攔截並記錄客戶端對連線物件的互動
  • 在 JTA 環境中,application server 可以裝飾連線 handle 以支援分散式交易語意

沒有 connection pooling 的 DataSource 工作流程#

  • 應用程式向 DataSource 請求連線
  • DataSource 使用底層驅動開啟實體連線並建立 TCP socket
  • 應用程式使用連線執行 SQL 陳述式
  • 關閉連線時,同時關閉底層的 TCP socket

Figure 2.5: DataSource without connection pooling

頻繁開啟與關閉資料庫連線是非常昂貴的操作。重用連線可以避免建立 TCP 連線的開銷、保留記憶體緩衝區,並減少客戶端 JVM 物件的垃圾回收。

為什麼 Connection Pooling 快這麼多?#

Connection pooling 的核心機制如下:

  • 當請求連線時,pool 先尋找未配置的連線 (unallocated connection)
  • 若有空閒連線,直接交給客戶端使用
  • 若沒有空閒連線,pool 嘗試增長到最大允許大小
  • 若已達最大值,請求會被排隊等待,超時後重試或拋出例外
  • 客戶端關閉邏輯連線時,連線歸還 pool 而非真正關閉實體連線

Figure 2.6: Connection acquisition request flow

效能比較#

書中測試比較了開關 1000 次資料庫連線的總耗時:

DB_ADB_BDB_CDB_DHikariCP
mean (ms)13.8296.47728.9101.5900.003458
p99 (ms)20.4329.94454.9523.0220.010263

使用 connection pooling 後,連線取得時間快了 二到四個數量級

Figure 2.7: DataSource connection

Connection pool 不僅減少連線取得時間,還能限制應用程式同時使用的連線數量,作為有界緩衝區來緩衝流量尖峰,避免耗盡所有資料庫資源。

Queuing Theory 容量規劃#

Little’s Law 是一個適用於穩態排隊系統的通用公式:

  • L = lambda x W
    • L:系統中的平均請求數(包含正在服務與排隊中的)
    • lambda:長期平均到達率
    • W:請求在系統中的平均停留時間

實際範例#

假設交易平均回應時間為 100ms,平均連線請求率為每秒 50 個:

  • L = 50 x 0.1 = 5 個連線請求
  • 因此 pool size 設為 5 即可在不排隊的情況下應付平均流量

在 queuing theory 中,throughput(離開率) 表示為:

  • mu = Ls / Ws = pool size / connection lease time

當到達率超過離開率時,溢出的請求必須排隊等待。書中示範了一個每秒 250 個請求、持續 3 秒的流量尖峰情境:750 個請求需要 15 秒才能完全處理,queue 大小成長到 700 筆。

Figure 2.8: Little's Law queue

Little’s Law 基於長期平均值,可能無法適當反映間歇性的流量爆發。在實際場景中,connection pool 必須能適應短期的流量尖峰,因此考量實際的 pool throughput 至關重要。

實務資料庫連線配置#

即使 queuing theory 提供了理論基礎,企業系統的動態行為遠比通用公式所能表達的複雜。持續監控連線使用模式並據此調整 pool 大小,才是務實的做法。

FlexyPool 監控指標#

作者開發的 FlexyPool 開源專案支援主流 connection pooling 框架,提供以下關鍵指標:

  • concurrent connection requests:同時被請求的連線數

Figure 2.9: Concurrent connection requests

  • concurrent connections:同時被使用的連線數

Figure 2.10: Concurrent connections

  • maximum pool size:pool 大小隨時間的變化

Figure 2.11: Maximum pool size

  • connection acquisition time:從 DataSource 取得連線的耗時

Figure 2.12: Connection acquisition time

  • retry attempts:連線取得的重試次數

Figure 2.13: Retry attempts

  • overall connection acquisition time:包含重試在內的總取得時間

Figure 2.14: Overall connection acquisition time

  • overflow pool size:pool 超過最大值的溢出量
  • connection lease time:連線從取得到釋放的持續時間

Figure 2.15: Connection lease time

FlexyPool Failover 策略#

  • Increment pool size on timeout:連線取得超時時自動增長 pool,直到 overflow 上限
  • Retrying attempts:對缺乏重試機制的 pool 提供自動重試功能

連線 Pool 監控的實務要點#

  • 平均值會抹平所有異常值,無法反映流量尖峰的影響
  • 百分位數 (percentiles) 在應用程式效能監控中更有價值,能揭示離群值並捕捉流量變化的即時效果
  • p99 concurrent connections 比平均值更適合用來決定正確的 pool 大小
  • 設定 idle connection timeout 很重要,讓 pool 能釋放未使用的連線給其他客戶端

長時間持有連線會增加其他客戶端的 connection acquisition time。長時間執行的交易可能持有資料庫鎖,增加序列化比例、阻礙平行處理。解決方案包括正確建立索引以加速慢查詢,或將 application-level transaction 拆分為多個較短的資料庫交易