在软件开发中,内存泄漏是一个常见的问题,尤其是在处理数据库连接和操作时。本文将探讨一个在特定情况下发生的内存泄漏问题,这个问题虽然在大多数情况下不会立即显现,但如果服务器代码24小时不间断运行,最终会导致内存耗尽。这个问题与之前讨论的CCommand
内存泄漏问题有所不同,其泄漏的内存量较小,但同样不容忽视。
背景信息:微软知识库描述了一个众所周知的内存泄漏问题及其解决方法。然而,还有一个微妙的内存泄漏问题尚未被广泛报道。很可能没有人像项目中那样使用ATL OLEDB。在项目中,性能至关重要,因此通常会准备带有参数的命令,并使用不同的参数运行它。使用它在一天中实时地向SQL Server写入交易订单和其他内容。这样,可以获得最佳性能,因为不需要一次又一次地生成SQL字符串并准备它。
谁会像这个例子中那样运行同一个命令一百万次?会。想法是准备带有参数的命令,并用不同的参数运行它。使用它在一天中实时地向SQL Server写入交易订单和其他内容。这样,可以获得最佳性能,因为不需要一次又一次地生成SQL字符串并准备它。
以下是使用C++和ATL OLEDB实现的代码示例:
CDataSource ds;
//Create and open the data source
CDBPropSet dbinit(DBPROPSET_DBINIT);
dbinit.AddProperty(DBPROP_INIT_DATASOURCE, "192.168.60.18");
dbinit.AddProperty(DBPROP_AUTH_USERID, "user");
dbinit.AddProperty(DBPROP_AUTH_PASSWORD, "");
dbinit.AddProperty(DBPROP_INIT_CATALOG, "master");
dbinit.AddProperty(DBPROP_AUTH_PERSIST_SENSITIVE_AUTHINFO, false);
dbinit.AddProperty(DBPROP_INIT_LCID, 1033L);
dbinit.AddProperty(DBPROP_INIT_PROMPT, static_cast
如果将这段代码复制到项目中并运行它(请使用正确的用户和密码为数据库环境),会发现任务管理器中的内存使用量以每秒10K的速度增加,尽管在另一篇文章中提到的主要内存泄漏已经在这里修复了。
这个问题的关键在于使用CDynamicParameterAccessor
而不是CDynamicAccessor
。当没有参数时,这个小内存泄漏就发生在CDynamicParameterAccessor
上。显然,如果非常小心地在不需要参数时使用CDynamicAccessor
,在存储过程有参数时使用CDynamicParameterAccessor
,可以绕过这个bug。然而,在某些情况下,不想区分它们,因为可能会将命令包装到一个新的类中,并希望无论存储过程是否有参数都能使用它。
问题的核心在于CCommand::Open()
调用了CDynamicParameterAccessor::BindParameters()
。以下是CDynamicParameterAccessor::BindParameters()
的实现:
HRESULT BindParameters(HACCESSOR* pHAccessor, ICommand* pCommand, void** ppParameterBuffer, bool fBindLength = false, bool fBindStatus = false) throw() {
// If we have already bound the parameters then just return the pointer to the parameter buffer
if (*pHAccessor != NULL) {
*ppParameterBuffer = m_pParameterBuffer;
return S_OK;
}
...
}
这段代码检查*pHAccessor
是否为NULL
。如果不是NULL
,意味着参数已经绑定,没有必要分配与参数相关的内存,也没有必要绑定参数访问器。这通常没问题。唯一的问题是,当存储过程没有参数时,BindEntries()
会失败。在这种情况下,*pHAccessor
仍然是NULL
,但所有与参数相关的内存都已分配。是的,因为ulParams
是0
,因为存储过程不需要参数。然而,AllocateParameterInfo()
会分配DBBINDING[0]
和OLECHAR*[0]
。这些操作实际上分配了一个好指针,内容长度为0
。在发布模式下,两者都消耗了16字节的内存。在调试版本中为64字节。由于每次*pHAccessor
是NULL
时,都会分配这块小内存。只有在析构函数中最后一次分配的内存才会被真正释放:
~CDynamicParameterAccessor() {
delete[] m_pParameterEntry;
if (m_ppParamName != NULL) {
CoTaskMemFree(*m_ppParamName);
delete[] m_ppParamName;
}
delete m_pParameterBuffer;
}