在 ODBC 驱动程序中开发连接池感知
本主题讨论开发 ODBC 驱动程序的详细信息,其中包含驱动程序应如何提供连接池服务的信息。
启用识别驱动程序的连接池
驱动程序必须实现以下 ODBC 服务提供程序接口 (SPI) 函数:
SQLSetConnectAttrForDbcInfo
SQLSetConnectInfo
SQLSetDriverConnectInfo
SQLGetPoolID
SQLRateConnection
SQLPoolConnect
SQLCleanupConnectionPoolID
有关详细信息,请参阅 ODBC 服务提供程序接口 (SPI) 参考。
驱动程序还必须实现以下现有函数,以便启用识别驱动程序的池:
函数 | 添加的功能 |
---|---|
SQLAllocHandle SQLFreeHandle SQLGetDiagField SQLGetDiagRec |
支持新的句柄类型:SQL_HANDLE_DBC_INFO_TOKEN(请参阅以下说明)。 |
SQLSetConnectAttr | 支持新的仅限设置的连接属性:SQL_ATTR_DBC_INFO_TOKEN,用于重置连接(请参阅以下说明)。 |
注意
识别驱动程序连接池不支持弃用的函数,例如 SQLError 和 SQLSetConnectOption。
池 ID
池 ID 是一个指针长度的驱动程序特定 ID,用于表示一组可互换使用的特定连接。 给定一组连接信息后,驱动程序应能够快速推断相应的池 ID。
例如,池 ID 应对服务器名称和凭据信息进行编码。 但是,不需要数据库名称,因为驱动程序可能能够重用连接,然后在比建立新连接更短的时间内更改数据库。
驱动程序应定义一组将构成池 ID 的键属性。 这些键属性的值可能来自连接属性、连接字符串和 DSN。 如果这些源中存在任何冲突,则应使用现有的特定于驱动程序的解决策略实现后向兼容性。
驱动程序管理器将为不同的池 ID 使用不同的池。 同一个池中的所有连接都是可重用的。 驱动程序管理器永远不会重用具有不同池 ID 的连接。
因此,驱动程序应为其定义的键属性中具有相同值的每个连接组分配唯一的池 ID。 如果驱动程序对键属性中具有不同值的两个连接使用相同的池 ID,驱动程序管理器仍会将它们放入同一个池(驱动程序管理器不知道有关特定于驱动程序的键属性的任何信息)。 这意味着驱动程序需要向驱动程序管理器报告,与一组不同键属性的连接在 SQLRateConnection 函数中不可重用。 这可能会降低性能,不建议这样做。
即使所有连接信息都匹配,驱动程序管理器也不会重用从另一个驱动程序环境分配的连接。 即使连接具有相同的池 ID,驱动程序管理器也会对不同的环境使用不同的池。 因此,池 ID 是其驱动程序环境的本地 ID。
用于从驱动程序获取池 ID 的函数是 SQLGetPoolID 函数。
连接分级
与建立新连接相比,可以通过在共用连接中重置某些连接信息(例如 DATABASE)来获得更好的性能。 因此,请勿使数据库名称位于键属性集中。 相反,可以为每个数据库创建单独的池,这在中间层应用程序中可能不太好,在这类应用程序中客户使用各种不同的连接字符串。
每当重用具有某些属性不匹配的连接时,应根据新的应用程序请求重置不匹配的属性,以便返回的连接与应用程序请求相同(请参阅 SQLSetConnectAttr 函数中对属性 SQL_ATTR_DBC_INFO_TOKEN 的讨论)。 但是,重置这些属性可能会降低性能。 例如,重置数据库需要对服务器进行网络调用。 因此,如果连接可用,则重用完全匹配的连接。
驱动程序中的评分函数可以使用新的连接请求评估现有连接。 例如,驱动程序的评分函数可以确定:
现有连接是否与请求完全匹配。
是否只有一些微不足道的不匹配(例如连接超时),此时无需与服务器通信来重置。
是否存在一些不匹配的属性,这种情况下需要与服务器通信以重置,但仍然会获得比新建连接更好的性能。
对于出现不匹配的属性,重置是否非常耗时(驱动程序的开发人员可考虑将此属性添加到用于生成池 ID 的键属性集)。
分数可能介于 0 到 100 之间,其中 0 表示无法重用,100 则表示完全匹配。 SQLRateConnection 是用于对连接进行评分的函数。
新建 ODBC 句柄 - SQL_HANDLE_DBC_INFO_TOKEN
为了支持驱动程序感知连接池,驱动程序需要连接信息来计算池 ID。 驱动程序还需要连接信息来比较新的连接请求与池中的连接。 每当池中没有任何连接可以重用时,驱动程序必须建立一个新的连接,因此需要连接信息。
由于连接信息可能来自多个源(连接字符串、连接属性和 DSN),因此驱动程序可能需要分析连接字符串并解决上述每个函数调用中这些源之间的冲突。
因此,引入了新的 ODBC 句柄:SQL_HANDLE_DBC_INFO_TOKEN。 使用 SQL_HANDLE_DBC_INFO_TOKEN 时,驱动程序无需多次分析连接字符串并解决连接信息中的冲突。 由于这是特定于驱动程序的数据结构,因此驱动程序可以存储连接信息或池 ID 等数据。
此句柄仅用于驱动程序管理器和驱动程序之间的接口。 应用程序无法直接分配此句柄。
此句柄的父句柄的类型为 SQL_HANDLE_ENV,这意味着驱动程序可以在连接信息解析期间从 HENV 句柄获取环境信息。
每当收到新的连接请求时,驱动程序管理器都会在确认驱动程序支持连接池感知后,分配 SQL_HANDLE_DBC_INFO_TOKEN 类型的句柄来存储连接信息。 使用句柄完成后(但在从 SQLDriverConnect 或 SQLConnect 返回除 SQL_STILL_EXECUTING 以外的某些返回代码之前),驱动程序管理器将释放句柄。 因此,句柄是在 SQLAllocHandle 调用后创建,并在 SQLFreeHandle 调用后销毁。 驱动程序管理器保证在释放其关联的 HENV 之前释放句柄(当 SQLDriverConnect 或 SQLConnect 返回错误时)。
驱动程序应修改以下函数以接受新的句柄类型 SQL_HANDLE_DBC_INFO_TOKEN:
驱动程序管理器保证多个线程不会同时使用同一个 SQL_HANDLE_DBC_INFO_TOKEN 句柄。 因此,此句柄的同步模型在驱动程序中可能非常简单。 驱动程序管理器在分配和释放 SQL_HANDLE_DBC_INFO_TOKEN 之前不会获取环境锁。
驱动程序管理器的 SQLAllocHandle 和 SQLFreeHandle 不会接受此新句柄类型。
SQL_HANDLE_DBC_INFO_TOKEN可能包含机密信息,例如凭据。 因此,驱动程序应在使用 SQLFreeHandle 释放此句柄之前,安全地清除包含敏感信息的内存缓冲区(使用 SecureZeroMemory)。 每当应用程序的环境句柄关闭时,所有关联的连接池都将关闭。
驱动程序管理器连接池评分算法
本节讨论驱动程序管理器连接池的评分算法。 驱动程序开发人员可以实现相同的算法以实现后向兼容性。 此算法可能不是最好的算法。 应根据实现优化此算法(否则,没有理由实现此功能)。
驱动程序管理器将为池中的每个连接返回一个从 0 到 100 的整体评分。 0 表示无法重用连接,100 表示完全匹配。 假设连接请求名为 hRequest,并且池中的现有连接命名为 hCandidate。 如果以下任一条件为 false,则无法为 hRequest 重用共用连接 hCandidate(驱动程序管理器将分配 0 的评分)。
hCandidate 和 hRequest 都来自 UNICODE API(如 SQLDriverConnectW)或 ANSI API(如 SQLDriverConnectA)。 (使用 ANSI API 和 UNICODE API(请参阅连接属性 SQL_ATTR_ANSI_APP)时,UNICODE 驱动程序的行为可能有所不同。)
hCandidate 和 hRequest 由同一函数创建;可能是 SQLDriverConnect,也可能是 SQLConnect。
使用 SQLDriverConnect 时,用于打开 hCandidate 的连接字符串应与 hRequest 相同。
使用 SQLConnect 时,用于打开 hCandidate 的 ServerName(或 DSN)、用户名和密码应与用于打开 hRequest 的相同。
当前线程的安全标识符 (SID) 应与用于打开 hCandidate 的 SID 相同。
对于登记和取消登记成本高昂的驱动程序(请参阅 SQLConnect 中对 SQL_DTC_TRANSITION_COST 的讨论),重用 hRequest 不得需要额外的登记或取消登记。
下表显示了不同应用场景的分数分配。
对共用连接与请求之间的连接属性的比较 | 无需登记/取消登记 | 需要额外的登记/取消登记 |
---|---|---|
目录 (SQL_ATTR_CURRENT_CATALOG) 不同 | 60 | 50 |
某些连接属性不同,但目录相同 | 90 | 70 |
所有连接属性完全匹配 | 100 | 80 |
序列图
此序列图显示了本主题中所述的基本池机制。 它仅显示 SQLDriverConnect 的使用,但 SQLConnect 情况类似。
状态图
此状态关系图显示了本主题中所述的连接信息令牌对象。 该关系图仅显示 SQLDriverConnect 的使用,但 SQLConnect 情况类似。 由于驱动程序管理器可能需要随时处理错误,因此驱动程序管理器可以针对任何状态调用 SQLFreeHandle。