在 ODBC 3.8 和 Windows 7 SDK 之前,非同步操作僅允許在語句函式上進行。 欲了解更多資訊,請參閱本主題後面的「 非同步執行語句操作」。
Windows 7 SDK 中的 ODBC 3.8 引入了連線相關操作的非同步執行。 更多資訊請參閱本主題後面的 「非同步執行連線操作 」章節。
在 Windows 7 SDK 中,對於非同步語句或連線操作,應用程式會透過輪詢方法判斷非同步操作已完成。 從 Windows 8 SDK 開始,你可以透過通知方法判斷非同步操作是否完成。 如需詳細資訊,請參閱非同步執行 (通知方法)。
預設情況下,驅動程式會同步執行 ODBC 函式;也就是說,應用程式呼叫一個函式,驅動程式在執行完該函式前不會將控制權還給應用程式。 然而,有些函式可以非同步執行;也就是說,應用程式呼叫該函式,驅動程式在最小處理後,將控制權還給應用程式。 應用程式可以在第一個函式仍在執行時呼叫其他函式。
大多數主要在資料來源上執行的函式都支援非同步執行,例如建立連線、準備與執行 SQL 語句、擷取元資料、擷取資料及提交交易的函式。 當資料來源執行的任務耗時較長,例如登入流程或對大型資料庫的複雜查詢時,它最為有用。
當應用程式執行一個啟用非同步處理的函式時,驅動程式會執行最少的處理(例如檢查參數錯誤),將處理交給資料來源,並以SQL_STILL_EXECUTING回傳碼將控制權回傳給應用程式。 應用程式接著執行其他任務。 為了判斷非同步函式何時完成,應用程式會定期輪詢驅動程式,並以與原始使用的相同參數呼叫函式。 如果函式仍在執行中,則回傳SQL_STILL_EXECUTING;如果執行完成,則回傳若同步執行時會返回的程式碼,例如 SQL_SUCCESS、SQL_ERROR 或 SQL_NEED_DATA。
函式是同步執行還是非同步執行,取決於驅動程式。 例如,假設結果集的元資料被快取在驅動程式中。 在這種情況下,執行 SQLDescribeCol 所需的時間非常短,驅動程式應該直接執行該函式,而非人為延遲執行。 另一方面,若驅動程式需要從資料來源取得元資料,則應在執行此事時將控制權回傳給應用程式。 因此,應用程式必須能處理非SQL_STILL_EXECUTING的回傳碼,當它首次非同步執行函式時。
非同步執行陳述句操作
以下語句函式可在資料來源上運作,並可非同步執行:
SQLBulkOperations
SQLColAttribute
SQLColumnPrivileges
SQLColumns
SQLDescribeCol
SQLDescribeParam
SQLExecDirect
SQLExecute
SQLFetch
非同步語句執行的控制取決於資料來源,可以根據每個語句或每個連線來進行控制。 也就是說,應用程式不會指定某個函式必須非同步執行,而是指定對特定語句執行的任何函式都必須非同步執行。 要查詢支援哪一種,應用程式會呼叫 SQLGetInfo ,選項為 SQL_ASYNC_MODE。 若支援連線層級非同步執行(針對語句柄),則回傳SQL_AM_CONNECTION;SQL_AM_STATEMENT 是否支援語句層級的非同步執行。
為了指定使用特定語句執行的函式要非同步執行,應用程式會呼叫帶有 SQL_ATTR_ASYNC_ENABLE 屬性的 SQLSetStmtAttr 並將其設為 SQL_ASYNC_ENABLE_ON。 若支援連線層級非同步處理,SQL_ATTR_ASYNC_ENABLE 語句屬性為唯讀,其值與該語句所分配連線的連線屬性相同。 驅動程式決定了敘述屬性的值是在陳述式配置時設定還是稍後設定。 嘗試設定時會返回 SQL_ERROR 和 SQLSTATE HYC00(未實作的可選功能)。
為了指定使用特定連線執行的函式要非同步執行,應用程式會呼叫帶有 SQL_ATTR_ASYNC_ENABLE 屬性的 SQLSetConnectAttr 並將其設為 SQL_ASYNC_ENABLE_ON。 所有未來分配到連線上的語句句柄都會啟用非同步執行;驅動程式定義是否能透過此動作啟用現有的語句句柄。 若 SQL_ATTR_ASYNC_ENABLE 設為 SQL_ASYNC_ENABLE_OFF,連線上的所有語句皆處於同步模式。 若在連線上有活躍敘述時啟用非同步執行,則會回傳錯誤。
為了確定驅動程式在特定連線下可支援的最大非同步並行敘述數量,應用程式會呼叫 SQLGetInfo 並啟用 SQL_MAX_ASYNC_CONCURRENT_STATEMENTS 選項。
以下程式碼示範了輪詢模型的運作方式:
SQLHSTMT hstmt1;
SQLRETURN rc;
// Specify that the statement is to be executed asynchronously.
SQLSetStmtAttr(hstmt1, SQL_ATTR_ASYNC_ENABLE, SQL_ASYNC_ENABLE_ON, 0);
// Execute a SELECT statement asynchronously.
while ((rc=SQLExecDirect(hstmt1,"SELECT * FROM Orders",SQL_NTS))==SQL_STILL_EXECUTING) {
// While the statement is still executing, do something else.
// Do not use hstmt1, because it is being used asynchronously.
}
// When the statement has finished executing, retrieve the results.
當函式以非同步方式執行時,應用程式可以呼叫其他語句中的函式。 應用程式也可以呼叫任何連線上的函式,除了與非同步敘述相關聯的連線。 但應用程式只能在敘述操作回傳 SQL_STILL_EXECUTING 後,呼叫原始函式以及以下的函式(使用語句控點或其關聯的連接、環境控點):
SQLCancelHandle(在語句控制代碼上)
例如,若應用程式呼叫任何帶有非同步語句或與該語句關聯的連線的函式,該函式會回傳 SQLSTATE HY010(函式序列錯誤)。
SQLHDBC hdbc1, hdbc2;
SQLHSTMT hstmt1, hstmt2, hstmt3;
SQLCHAR * SQLStatement = "SELECT * FROM Orders";
SQLUINTEGER InfoValue;
SQLRETURN rc;
SQLAllocHandle(SQL_HANDLE_STMT, hdbc1, &hstmt1);
SQLAllocHandle(SQL_HANDLE_STMT, hdbc1, &hstmt2);
SQLAllocHandle(SQL_HANDLE_STMT, hdbc2, &hstmt3);
// Specify that hstmt1 is to be executed asynchronously.
SQLSetStmtAttr(hstmt1, SQL_ATTR_ASYNC_ENABLE, SQL_ASYNC_ENABLE_ON, 0);
// Execute hstmt1 asynchronously.
while ((rc = SQLExecDirect(hstmt1, SQLStatement, SQL_NTS)) == SQL_STILL_EXECUTING) {
// The following calls return HY010 because the previous call to
// SQLExecDirect is still executing asynchronously on hstmt1. The
// first call uses hstmt1 and the second call uses hdbc1, on which
// hstmt1 is allocated.
SQLExecDirect(hstmt1, SQLStatement, SQL_NTS); // Error!
SQLGetInfo(hdbc1, SQL_UNION, (SQLPOINTER) &InfoValue, 0, NULL); // Error!
// The following calls do not return errors. They use a statement
// handle other than hstmt1 or a connection handle other than hdbc1.
SQLExecDirect(hstmt2, SQLStatement, SQL_NTS); // OK
SQLTables(hstmt3, NULL, 0, NULL, 0, NULL, 0, NULL, 0); // OK
SQLGetInfo(hdbc2, SQL_UNION, (SQLPOINTER) &InfoValue, 0, NULL); // OK
}
當應用程式呼叫函式以判斷是否仍在非同步執行時,必須使用原始的語句句柄。 這是因為非同步執行是以每語句為單位追蹤的。 應用程式也必須為其他參數提供有效值——原始參數即可——以通過驅動程式管理員的錯誤檢查。 然而,當驅動程式檢查語句句柄並確定該語句是非同步執行時,會忽略所有其他參數。
當函式以非同步方式執行時——也就是在回傳 SQL_STILL_EXECUTING 後且返回不同程式碼之前——應用程式可以透過呼叫相同的語句 handle 的 SQLCancel 或 SQLCancelHandle 來取消該函式。 這並不保證會取消函式執行。 例如,功能可能已經完成。 此外, SQLCancel 或 SQLCancelHandle 回傳的程式碼僅表示取消函式是否成功,並未顯示是否真的取消了函式。 為了判斷函式是否被取消,應用程式會再次呼叫該函式。 若函式被取消,則回傳 SQL_ERROR 及 SQLSTATE HY008(操作取消)。 如果函式沒有被取消,它會回傳另一段程式碼,例如 SQL_SUCCESS、SQL_STILL_EXECUTING 或 SQL_ERROR,且 SQLSTATE 不同。
當驅動程式支援語句層級非同步處理時,若要停用特定語句的非同步執行,應用程式會呼叫帶有 SQL_ATTR_ASYNC_ENABLE 屬性的 SQLSetStmtAttr 並將其設為 SQL_ASYNC_ENABLE_OFF。 若驅動程式支援連線層級非同步處理,應用程式會呼叫 SQLSetConnectAttr 將 SQL_ATTR_ASYNC_ENABLE 設為 SQL_ASYNC_ENABLE_OFF,從而禁用連線上所有語句的非同步執行。
應用程式應在原始函式的重複迴圈中處理診斷記錄。 若在執行非同步函式時呼叫 SQLGetDiagField 或 SQLGetDiagRec ,則會回傳目前的診斷記錄清單。 每次重複原始函式呼叫時,都會清除先前的診斷紀錄。
非同步執行連線操作
在 ODBC 3.8 之前,允許非同步執行用於與語句相關的操作,如準備、執行和取取,以及目錄中繼資料操作。 從 ODBC 3.8 開始,像連接、斷開、提交和回滾這些與連線相關的操作也可以非同步執行。
欲了解更多 ODBC 3.8 資訊,請參閱 ODBC 3.8 的新內容。
非同步執行連線操作在以下情境下非常有用:
當少量執行緒管理大量資料速率極高的裝置時, 為了最大化響應性與可擴展性,所有操作都應是非同步的。
當你想在多個連線上重疊資料庫操作以減少傳輸時間時,
高效的非同步 ODBC 呼叫以及取消連線操作的能力,使應用程式能允許使用者取消任何緩慢操作,而無需等待逾時。
以下這些在連接句柄上操作的函式現在可以非同步執行:
為了判斷驅動程式是否支援這些函式的非同步操作,應用程式會呼叫 SQLGetInfo 並啟用 SQL_ASYNC_DBC_FUNCTIONS。 若支援非同步操作,則回傳SQL_ASYNC_DBC_CAPABLE。 若不支援非同步操作,則回傳SQL_ASYNC_DBC_NOT_CAPABLE。
為了指定以特定連線執行的函式要非同步執行,應用程式會呼叫 SQLSetConnectAttr 並將 SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE 屬性設為 SQL_ASYNC_DBC_ENABLE_ON。 在建立連線前設定連線屬性總是同步執行。 此外,設定連線屬性SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE SQLSetConnectAttr 的操作總是同步執行。
應用程式可以在建立連線前啟用非同步操作。 由於驅動程式管理器無法在建立連線前決定該使用哪個驅動程式,因此在 SQLSetConnectAttr 中,驅動程式管理器總是會回傳成功。 然而,若 ODBC 驅動程式不支援非同步操作,可能會導致無法連線。
一般而言,特定連線的 handle 或 statement handle 最多只能有一個非同步執行的函式。 然而,一個連線句柄可以有多個相關的語句句柄。 如果連線句柄上沒有非同步操作正在執行,相關的語句句柄可以執行非同步操作。 類似地,如果在任何相關的語句句柄上沒有非同步操作正在進行,則可以在連線句柄上進行非同步操作。 嘗試使用正在執行非同步操作的代柄執行非同步操作時,會回傳 HY010,「函式序列錯誤」。
若連線操作傳回 SQL_STILL_EXECUTING,應用程式僅能針對該連線控制代碼呼叫原始函數及以下函數:
SQLCancelHandle (連線柄)
SQLGetDiagField
SQLGetDiagRec
SQLAllocHandle (分配 ENV/DBC)
SQLAllocHandleStd (分配 ENV/DBC)
SQLGetEnvAttr
SQLGetConnectAttr
SQLDataSources
SQLDrivers
SQLGetInfo
SQLGetFunctions
應用程式應在原始函式的重複迴圈中處理診斷記錄。 若在執行非同步函式時呼叫 SQLGetDiagField 或 SQLGetDiagRec,則會回傳目前的診斷記錄清單。 每次重複原始函式呼叫時,都會清除先前的診斷紀錄。
如果連線是非同步開啟或關閉,當應用程式在原始函式呼叫中收到SQL_SUCCESS或SQL_SUCCESS_WITH_INFO時,操作即告完成。
ODBC 3.8 新增了一個函式,名為 SQLCancelHandle。 此函式會取消六個連線函式(SQLBrowseConnect、 SQLConnect、 SQLDisconnect、 SQLDriverConnect、 SQLEndTran 及 SQLSetConnectAttr)。 應用程式應呼叫 SQLGetFunctions ,以判斷驅動程式是否支援 SQLCancelHandle。 與 SQLCancel 相同,若 SQLCancelHandle 回傳成功,並不代表該操作已被取消。 應用程式應再次呼叫原始函式以判斷該操作是否已被取消。 SQLCancelHandle 允許你取消對連線句柄或語句句柄的非同步操作。 使用 SQLCancelHandle 取消對語句句柄的操作,與呼叫 SQLCancel 是相同的。
無須同時支援 SQLCancelHandle 和非同步連線作業。 驅動程式可以支援非同步連線作業,但不支援 SQLCancelHandle,反之亦然。
非同步連線操作與 SQLCancelHandle 也可用於 ODBC 3.x 與 ODBC 2.x 應用程式,搭配 ODBC 3.8 驅動程式與 ODBC 3.8 驅動管理器。 關於如何讓舊應用程式在較新的 ODBC 版本中使用新功能,請參見 相容矩陣。
連線池化
啟用連線池時,非同步操作僅在使用 SQLConnect 和 SQLDriverConnect 建立連線,以及使用 SQLDisconnect 關閉連線時,得到最低限度的支援。 但應用程式仍應能處理 SQLConnect、 SQLDriverConnect 和 SQLDisConnect 的 SQL_STILL_EXECUTING 回傳值。
啟用連線池時,支援 SQLEndTran 與 SQLSetConnectAttr 進行非同步操作。
範例
A。 啟用連線函式的非同步執行
以下範例說明如何使用 SQLSetConnectAttr 來啟用與連線相關的非同步執行。
BOOL AsyncConnect (SQLHANDLE hdbc)
{
SQLRETURN r;
SQLHANDLE hdbc;
// Enable asynchronous execution of connection functions.
// This must be executed synchronously, that is r != SQL_STILL_EXECUTING
r = SQLSetConnectAttr(
hdbc,
SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE,
reinterpret_cast<SQLPOINTER> (SQL_ASYNC_DBC_ENABLE_ON),
0);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
{
return FALSE;
}
TCHAR szConnStrIn[256] = _T("DSN=AsyncDemo");
r = SQLDriverConnect(hdbc, NULL, (SQLTCHAR *) szConnStrIn, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
if (r == SQL_ERROR)
{
// Use SQLGetDiagRec to process the error.
// If SQLState is HY114, the driver does not support asynchronous execution.
return FALSE;
}
while (r == SQL_STILL_EXECUTING)
{
// Do something else.
// Check for completion, with the same set of arguments.
r = SQLDriverConnect(hdbc, NULL, (SQLTCHAR *) szConnStrIn, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
}
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
{
return FALSE;
}
return TRUE;
}
B. 非同步提交操作
此範例展示了非同步提交操作。 回滾操作也可以用這種方式完成。
BOOL AsyncCommit ()
{
SQLRETURN r;
// Assume that SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE is SQL_ASYNC_DBC_ENABLE_ON.
r = SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);
while (r == SQL_STILL_EXECUTING)
{
// Do something else.
// Check for completion with the same set of arguments.
r = SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);
}
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
{
return FALSE;
}
return TRUE;
}