7

我们有一个通过 MFC (VS2010) 中的 CDatabase/CRecordset 使用 ODBC 的应用程序。我们实现了两个后端。MSSQL 和 MySQL。

现在,当我们使用 MSSQL(使用 Native Client 10.0)时,通过慢速链接(例如 VPN)使用 SELECT 检索记录非常慢。MySQL ODBC 驱动程序不会表现出这种讨厌的行为。

例如:

CRecordset r(&m_db);
r.Open(CRecordset::snapshot, L"SELECT a.something, b.sthelse FROM TableA AS a LEFT JOIN TableB AS b ON a.ID=b.Ref");
r.MoveFirst();
while(!r.IsEOF())
{
    // Retrieve
    CString strData;
    crs.GetFieldValue(L"a.something", strData);
    crs.MoveNext();
}

现在,有了 MySQL 驱动程序,一切都可以正常运行了。查询返回,一切都快如闪电。但是,对于 MSSQL Native Client,事情会变慢,因为在每个 MoveNext() 上,驱动程序都会与服务器通信。

我认为这是由于服务器端游标,但我没有找到禁用它们的方法。我试过使用:

::SQLSetConnectAttr(m_db.m_hdbc, SQL_ATTR_ODBC_CURSORS, SQL_CUR_USE_ODBC, SQL_IS_INTEGER);

但这也无济于事。SQL Profiler 中仍然有 sp_cursorfetch() 等的长期运行的 exec。我还尝试了一个带有 SQLAPI 和批量提取的小型参考项目,但它也在 FetchNext() 中挂起很长时间(即使结果集中只有一条记录)。然而,这只发生在使用 LEFT JOINS、表值函数等 的查询上。请注意,查询不会花费那么长时间- 通过 SQL Studio 在相同的连接上执行相同的 SQL 会在合理的时间内返回。

问题1:是否有可能以某种方式让本机客户端访问在本地“缓存”所有结果以与 MySQL 驱动程序类似的方式使用本地游标?

也许这完全是错误的方法,但我不知道该怎么做。

我们想要的只是一次从 SELECT 中检索所有数据,然后在下一次查询之前不再与服务器通信。我们不关心记录集更新、删除等或任何废话。我们只想检索数据。我们获取该记录集,获取所有数据,然后将其删除。

问题 2:有没有更有效的方法来使用 ODBC 在 MFC 中检索数据?

4

1 回答 1

3

我对这个问题做了更多的修改,发现了这两个链接:

MSDN 链接

MSDN 博客

在第一个链接中,它描述了 Native Client 10 仅在更改默认选项时才使用服务器端游标:

在执行 SQL 语句时将这些选项设置为默认值时,SQL Server Native Client ODBC 驱动程序不使用服务器游标来实现结果集;相反,它使用默认结果集。

链接 2 是一个博客,它是一个 SQL Dev 博客,上面写着:

事实证明,开发人员并没有明确要求服务器光标。但是当他阻止提取时,作为一个副作用,SQL Server ODBC 驱动程序要求一个服务器游标......这是出乎意料的!

是的,这当然是出乎意料的……

如何通过默认结果集(消防水带光标)而不是服务器光标进行块提取?

现在,解决方案的实现是这样的:

代替:

// crs is a CRecordSet
crs.Open(CRecordset::snapshot, L"SELECT something...");

做这个:

// crs is a CRecordSet
crs.Open(CRecordset::forwardOnly, L"SELECT something...");

这个简单的更改不会触发服务器端游标的创建,而是模仿 MySQL 驱动程序的行为。

缺点是现在您无法通过(Microsoft 推荐的)方式检索行数:

while(crs.MoveNext()) nCount++;

无论如何,这是一个坏主意。此外, ::SQLGetRowCount() 将不再始终有效。

我已经解决了这样的问题(这应该适用于任何 ANSI 兼容的 SQL 源):

//strQuery is the random query passed to CountRows()
std::wstringstream ssQuery;
ssQuery << L"SELECT COUNT(*) AS Count FROM (" << strQuery << L") AS t";
CRecordset crs(&m_Database);
crs.Open(CRecordset::forwardOnly, ssQuery.str().c_str());
// Now retrieve the only "Count" field from the recordset.

我希望这对将来的其他人有所帮助。

于 2012-10-14T20:57:11.890 回答