在 Client/Server 架構下,VB/Delphi/PowerBuilder 等前端工具透過 ODBC 2.0 存取 SQL Server。開發工具提供的資料控制項/物件預設採用 **非同步模式**,若開發者不了解同步/非同步差異,極易在「發出查詢 → 立即讀取結果」路徑上踩中 **資料未就緒** 導致的邏輯錯誤或系統死鎖。本文系統剖析兩種模式機制,並給出兩種標準解決方案。
| 維度 | 同步執行模式 | 非同步執行模式 |
|---|---|---|
| 控制流 | 呼叫端 **阻塞等待** 伺服器完成並返回結果 | 呼叫端 **立即繼續** 執行下一行,結果稍後到達 |
| 查詢範例 | `SELECT * FROM T` → 等待 → 拿到 Recordset → 下一行 | `SELECT * FROM T` → 立即執行下一行 → 稍後輪詢/回調取結果 |
| 優點 | 邏輯直觀、代碼簡單、易除錯 | 高併發、UI 不凍結、可並行多條 SQL |
| 風險 | 長查詢凍結 UI/執行緒 | **後續代碼讀取未就緒結果 → 邏輯錯誤/死鎖** |
| 預設模式 | 手動啟用 | **ODBC 2.0 / 多數前端工具預設** |
// 開發者心理模型 (同步)
ExecSQL("SELECT * FROM Orders"); // 以為這裡會等待
Process(Recordset); // 實際上 Recordset 可能仍為空/舊資料
// 結果:處理空資料、觸發存取違規、或資料庫連線死鎖
根因:**非同步 API 首次呼叫返回 `SQL_STILL_EXECUTING`**,開發者未檢查返回碼直接使用結果集。
呼叫 `SQLSetStmtOption` 關閉非同步旗標:
RETCODE rc = SQLSetStmtOption(hStmt, SQL_ASYNC_ENABLE, 0); // 0 = 關閉非同步 = 同步
if (rc != SQL_SUCCESS) { /* 錯誤處理 */ }
// 後續 SQLExecDirect/SQLExecute 皆為同步阻塞
`SQLExecDirect`、`SQLExecute`、`SQLFetch`、`SQLGetData`、`SQLParamData`、`SQLPutData`、`SQLMoreResults`、`SQLCloseCursor` 等 20+ 函數。
// 1. 啟用非同步
SQLSetStmtOption(hStmt, SQL_ASYNC_ENABLE, 1);
// 2. 發起查詢
RETCODE rc = SQLExecDirect(hStmt, (UCHAR*)"SELECT * FROM Employees", SQL_NTS);
// 3. 首次呼叫通常返回 SQL_STILL_EXECUTING
if (rc == SQL_STILL_EXECUTING) {
// 4. 執行其他工作 (UI 回應、發起其他查詢、計算...)
DoOtherWork();
// 5. 稍後重新呼叫 **相同參數** 輪詢
rc = SQLExecDirect(hStmt, (UCHAR*)"SELECT * FROM Employees", SQL_NTS);
// 注意:參數必須與首次完全一致,ODBC 會忽略 SQL 字串但要求句柄相同
}
// 6. 判斷最終結果
if (rc == SQL_SUCCESS) {
// 查詢完成,安全處理結果集
while (SQLFetch(hStmt) == SQL_SUCCESS) { ProcessRow(); }
} else if (rc == SQL_STILL_EXECUTING) {
// 仍在執行,稍後再輪詢或註冊完成通知
} else {
// 真正失敗
HandleError(rc);
}
| 誤區 | 後果 | 正確做法 |
|---|---|---|
| 非同步模式下首次呼叫不檢查返回碼 | 拿到 SQL_STILL_EXECUTING 卻當成 SUCCESS 處理 | **永遠檢查返回碼**,`SQL_STILL_EXECUTING` 視為「已受理,稍後再來」 |
| 輪詢時 SQL 字串/參數與首次不一致 | ODBC 視為新請求,導致邏輯混亂/資源洩漏 | 輪詢 **必須傳入相同句柄、相同參數** (SQL 字串內容會被忽略但必須提供) |
| 多執行緒共用同一 hStmt | ODBC 陳述式句柄 **非執行緒安全** | 每執行緒獨立 `SQLAllocHandle(SQL_HANDLE_STMT, ...)` |
| 非同步查詢未取消就關閉連線 | 伺服器端查詢繼續跑,資源洩漏、鎖佔用 | 關閉前呼叫 `SQLCancel(hStmt)` 取消進行中查詢 |
ODBC 2.0 為 1990 年代技術,新專案建議:
ODBC 非同步模式是 **雙面刃**:用得好實現高併發響應,用不好產生難以重現的死鎖/邏輯錯誤。核心心法只有兩條:同步模式下相信阻塞;非同步模式下相信返回碼。掌握 `SQLSetStmtOption` 切換模式、`SQL_STILL_EXECUTING` 輪詢語義、`SQLCancel` 取消語義,即可在 C/S 架構下從容駕馭資料庫存取時序。