使用 ADO (ActiveX Data Objects) 連接 SQL Server 時,若以 Connection.Execute 方法直接將結果集指派給 Recordset 變數,會觸發隱式連接:每次執行 SQL 陳述式都會在背景建立新的實體連接,嚴重影響效能與連接池複用。
Dim cnn As New ADODB.Connection
Dim rs As New ADODB.Recordset
cnn.Open "Provider=SQLOLEDB.1;Data Source=svr;Initial Catalog=freemail;User ID=sa;..."
sql = "SELECT * FROM users"
Set rs = cnn.Execute(sql) ' 第一次:建立連接 1
Set rs = cnn.Execute(sql) ' 第二次:建立連接 2 (隱式)
Set rs = cnn.Execute(sql) ' 第三次:建立連接 3 (隱式)
SQL Server Profiler 會顯示每次 Execute 前都出現 Audit Login 事件,代表建立新連接。且這些隱式連接不會進入連接池,執行完畢即銷毀,極度浪費資源。
SQLOLEDB Provider 預設使用 Forward-Only, Read-Only 游標(預設游標類型)。當程式碼將 `Execute` 回傳的 Recordset 指派給變數時,Provider 為了支援同一連接上多個待處理結果集,會自動建立第二個實體連接。此行為由 DBPROP_MULTIPLECONNECTIONS 屬性控制,預設為 True。
參考 Microsoft KB 文件:PRB: Implicit Connections Created by the SQL Server OLE DB Provider (SQLOLEDB) Are Not Pooled
在開啟 Recordset 前明確設定 CursorType 為非預設類型(如 adOpenStatic、adOpenKeyset、adOpenDynamic),即可避免隱式連接:
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim rs2 As New ADODB.Recordset
cn.Open "Provider=SQLOLEDB.1;Data Source=svr;Initial Catalog=freemail;User ID=sa;..."
' 正確做法:設定游標類型再開啟
rs.CursorType = adOpenStatic
rs.ActiveConnection = cn
rs.Open "SELECT * FROM users"
rs2.CursorType = adOpenStatic
rs2.ActiveConnection = cn
rs2.Open "SELECT * FROM users"
' 同一連接 cn 可重複使用,無隱式連接產生
| CursorType | 特性 | 適用場景 |
|---|---|---|
| adOpenForwardOnly (預設) | 唯讀、僅能向前捲動、效能最佳 | 單向讀取大量資料、報表產生 |
| adOpenStatic | 靜態複本、可捲動、不反映他人變更 | 需隨機存取、資料量中等、離線操作 |
| adOpenKeyset | 鍵集游標、可捲動、反映資料變更、不見新增列 | 需即時看到更新/刪除、中大型資料集 |
| adOpenDynamic | 動態游標、完整反映所有變更、成本最高 | 高併發環境需即時一致性、小資料集 |
adOpenForwardOnly 效能最好,但務必在同一時間只保留一個作用中 Recordset,或在下一次 Execute 前明確 rs.Close。adOpenStatic 並指派 ActiveConnection。SqlDataReader 為前向唯讀,DataAdapter.Fill(DataTable) 自動管理連接池,完全避免此問題。