在 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
异步语句的执行是根据数据源的不同,可以在每个语句或每个连接的基础上进行控制。 也就是说,应用程序指定不是异步执行特定函数,而是要异步执行对特定语句执行的任何函数。 若要了解支持哪一项,应用程序会使用SQL_ASYNC_MODE选项调用 SQLGetInfo 。 如果支持连接级异步执行(对于语句句柄),则返回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,则连接上的所有语句都处于同步模式。 如果在连接上有活动语句时启用异步执行,则返回错误。
若要在异步模式下确定驱动程序可以在给定连接上支持的最大活动并发语句数,应用程序使用 SQL_MAX_ASYNC_CONCURRENT_STATEMENTS 选项调用 SQLGetInfo 。
以下代码演示轮询模型的工作原理:
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之后以及返回其他代码之前)应用程序可以通过使用相同的语句句柄调用 SQLCancel 或 SQLCancelHandle 来取消它。 这不能保证取消函数执行。 例如,该函数可能已经完成。 此外,SQLCancel 或 SQLCancelHandle 返回的代码仅指示尝试取消函数是否成功,而不是是否实际取消了函数。 若要确定函数是否已取消,应用程序将再次调用该函数。 如果函数已取消,它将返回SQL_ERROR和 SQLSTATE HY008(作已取消)。 如果未取消函数,它将返回另一个代码,例如SQL_SUCCESS、SQL_STILL_EXECUTING或具有不同 SQLSTATE 的SQL_ERROR。
若要在驱动程序支持语句级异步处理时禁用特定语句的异步执行,应用程序使用 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调用和取消连接操作的功能使应用程序能够允许用户取消任何慢速操作,而无需等待超时。
现在,可以异步执行以下对连接句柄进行操作的函数:
若要确定驱动程序是否支持对这些函数执行异步作,应用程序使用 SQL_ASYNC_DBC_FUNCTIONS 调用 SQLGetInfo 。 如果支持异步操作,则返回SQL_ASYNC_DBC_CAPABLE。 如果不支持异步操作,则返回SQL_ASYNC_DBC_NOT_CAPABLE。
若要指定使用特定连接执行的函数是异步执行的,应用程序将调用 SQLSetConnectAttr 并将SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE属性设置为SQL_ASYNC_DBC_ENABLE_ON。 在建立连接之前设置连接属性始终同步执行。 此外,使用 SQLSetConnectAttr 设置连接属性 SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE 的操作始终同步执行。
应用程序可以在建立连接之前启用异步作。 由于驱动程序管理器在建立连接之前无法确定要使用的驱动程序,因此驱动程序管理器将始终在 SQLSetConnectAttr 中返回成功。 但是,如果 ODBC 驱动程序不支持异步作,它可能无法连接。
一般情况下,最多可以有一个异步执行与特定连接句柄或语句句柄关联的函数。 但是,连接句柄可以拥有多个关联的语句句柄。 如果在连接句柄上没有执行异步操作,则关联的语句句柄可以执行异步操作。 同样,如果在任何关联的语句句柄上没有正在进行异步操作,则可以对连接句柄执行异步操作。 尝试使用当前正在执行异步操作的句柄来执行另一个异步操作将返回 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,反之亦然。
ODBC 3.x 和 ODBC 2.x 应用程序也可以使用 ODBC 3.8 驱动程序和 ODBC 3.8 驱动程序管理器的异步连接操作以及 SQLCancelHandle。 有关如何使旧应用程序能够在更高版本的 ODBC 版本中使用新功能的信息,请参阅 兼容性矩阵。
连接池
每当启用连接池时,异步操作仅为建立连接(使用 SQLConnect 和 SQLDriverConnect)和使用 SQLDisconnect 关闭连接提供最低限度的支持。 但应用程序仍应能够处理 SQLConnect、SQLDriverConnect 和 SQLDisconnect 的SQL_STILL_EXECUTING返回值。
启用连接池后,SQLEndTran 和 SQLSetConnectAttr 支持异步操作。
例子
答: 启用连接函数的异步执行
以下示例演示如何使用 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;
}