准备好的语句:尽管我们删除了结果集,但内存仍然分配给它



在我的应用程序(使用MySQL Connector/C++在Windows8上开发)中,我正在创建准备好的语句,并仅在应用程序结束时删除它们。但在应用程序运行时,我执行查询并只删除结果集。

然而,我观察到很多内存仍然被分配,我觉得这比预期的要多。我用Visual Leak Detector进行了检查,令我惊讶的是,尽管我适当地删除了它们,但我发现泄漏显示在结果集指针中。

所以我编写了这个演示程序。这就是创建准备好的语句,创建查询,获取结果,删除结果(但不要在最后删除准备好的声明,这样我们就可以看到泄漏),然后退出。这是演示代码MySQL.cpp:

#include "stdafx.h"
#include <conio.h>
#define CPPCONN_LIB_BUILD // We must define this as we are linking mysql connector in static library. It directs build_config.h to not to put __declspec(dllimport) before function declarations.
#include <driver/mysql_connection.h>
#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>
#include <cppconn/prepared_statement.h>
#include <vld.h> // Visual memory leak detector
int _tmain(int argc, _TCHAR* argv[])
{
    sql::Connection *pConnection = NULL;
    sql::ResultSet *pResultSet = NULL;
    sql::PreparedStatement *pPreparedStatement = NULL;
    sql::Driver *driver = NULL;
    /* Create a connection */
    driver = get_driver_instance();
    pConnection = driver->connect("tcp://127.0.0.1:3306", "username", "password");
    pConnection->setSchema("MYDB");
    pConnection->setAutoCommit(0);
    sql::ResultSet* pResultSet;
    pPreparedStatement = pConnection->prepareStatement ("select * from mytable where mycolumn > ?"); // mytable has mycolumn that contains 1000 numbers starting from 1
    pPreparedStatement->setInt(1, 1);
    pResultSet= pPreparedStatement->executeQuery();
    int count = pResultSet->rowsCount();
    printf("nTotal rows found %d", count);
    delete pResultSet;
    // delete pPreparedStatement; // Let's not delete prepared statement to see demo of memory leak in pResultSet
    delete pConnection;
    printf ("nDone! Quitting...");
    return 0;
}

下面是报告:

Visual Leak Detector Version 2.4RC2 installed.
    Aggregating duplicate leaks.
    Suppressing data dumps.
    Outputting the report to E:MySQLmemory_leak_report.txt
WARNING: Visual Leak Detector detected memory leaks!
---------- Block 65 at 0x0000000068D87EB0: 8 bytes ----------
  Leak Hash: 0x38615834, Count: 1, Total 8 bytes
  Call Stack (TID 4628):
    0x00000000C3EC5630 (File and line number not available): ntdll.dll!RtlAllocateHeap
    f:ddvctoolscrt_bldself_64_amd64crtsrcnew.cpp (59): MySQLTrials.exe!operator new + 0xA bytes
    0x00000000DF30AEE4 (File and line number not available): MySQLTrials.exe!sql::mysql::util::Singleton<sql::mysql::NativeAPI::LibmysqlStaticProxy>::theInstance + 0x44 bytes
    0x00000000DF306DB1 (File and line number not available): MySQLTrials.exe!sql::mysql::NativeAPI::getCApiHandle + 0x41 bytes
    0x00000000DF2AA5AC (File and line number not available): MySQLTrials.exe!sql::mysql::NativeAPI::MySQL_NativeDriverWrapper::MySQL_NativeDriverWrapper + 0x5C bytes
    0x00000000DF2AA51D (File and line number not available): MySQLTrials.exe!sql::mysql::NativeAPI::createNativeDriverWrapper + 0x4D bytes
    0x00000000DF28401B (File and line number not available): MySQLTrials.exe!sql::mysql::MySQL_Driver::MySQL_Driver + 0x8B bytes
    0x00000000DF28456F (File and line number not available): MySQLTrials.exe!sql::mysql::get_driver_instance_by_name + 0x18F bytes
    0x00000000DF284681 (File and line number not available): MySQLTrials.exe!sql::mysql::get_driver_instance + 0x21 bytes
    0x00000000DF283E1A (File and line number not available): MySQLTrials.exe!get_driver_instance + 0x1A bytes
    e:mysqlmysql.cpp (22): MySQLTrials.exe!wmain + 0x5 bytes
    f:ddvctoolscrt_bldself_64_amd64crtsrccrt0.c (240): MySQLTrials.exe!__tmainCRTStartup + 0x19 bytes
    f:ddvctoolscrt_bldself_64_amd64crtsrccrt0.c (164): MySQLTrials.exe!wmainCRTStartup
    0x00000000C1CF167E (File and line number not available): KERNEL32.DLL!BaseThreadInitThunk + 0x1A bytes
    0x00000000C3EDC3F1 (File and line number not available): ntdll.dll!RtlUserThreadStart + 0x21 bytes

---------- Block 413 at 0x0000000068D90FF0: 40 bytes ----------
  Leak Hash: 0x7614B12C, Count: 1, Total 40 bytes
  Call Stack (TID 4628):
    0x00000000C3EC5630 (File and line number not available): ntdll.dll!RtlAllocateHeap
    f:ddvctoolscrt_bldself_64_amd64crtsrcnew.cpp (59): MySQLTrials.exe!operator new + 0xA bytes
    0x00000000DF30C576 (File and line number not available): MySQLTrials.exe!sql::mysql::NativeAPI::MySQL_NativeConnectionWrapper::stmt_init + 0x86 bytes
    0x00000000DF28E730 (File and line number not available): MySQLTrials.exe!sql::mysql::MySQL_Connection::prepareStatement + 0xC0 bytes
    e:mysqlmysql.cpp (30): MySQLTrials.exe!wmain + 0x30 bytes
    f:ddvctoolscrt_bldself_64_amd64crtsrccrt0.c (240): MySQLTrials.exe!__tmainCRTStartup + 0x19 bytes
    f:ddvctoolscrt_bldself_64_amd64crtsrccrt0.c (164): MySQLTrials.exe!wmainCRTStartup
    0x00000000C1CF167E (File and line number not available): KERNEL32.DLL!BaseThreadInitThunk + 0x1A bytes
    0x00000000C3EDC3F1 (File and line number not available): ntdll.dll!RtlUserThreadStart + 0x21 bytes

---------- Block 241 at 0x0000000068D93910: 16 bytes ----------
  Leak Hash: 0x447A29BE, Count: 1, Total 16 bytes
  Call Stack (TID 4628):
    0x00000000C3EC5630 (File and line number not available): ntdll.dll!RtlAllocateHeap
    c:program files (x86)microsoft visual studio 11.0vcincludexmemory0 (592): MySQLTrials.exe!std::allocator<std::_Container_proxy>::allocate
    0x00000000DF28B052 (File and line number not available): MySQLTrials.exe!std::_Wrap_alloc<std::allocator<std::_Container_proxy> >::allocate + 0x32 bytes
    0x00000000DF303CA7 (File and line number not available): MySQLTrials.exe!std::_Deque_alloc<0,std::_Deque_base_types<sql::mysql::MySQL_DebugEnterEvent const * __ptr64,std::allocator<sql::mysql::MySQL_DebugEnterEvent const * __ptr64> > >::_Alloc_proxy + 0x37 bytes
    0x00000000DF303991 (File and line number not available): MySQLTrials.exe!std::_Deque_alloc<0,std::_Deque_base_types<sql::mysql::MySQL_DebugEnterEvent const * __ptr64,std::allocator<sql::mysql::MySQL_DebugEnterEvent const * __ptr64> > >::_Deque_alloc<0,std::_Deque_base_types<sql::mysql::MySQL_DebugEnterEvent const * __ptr64,std + 0x41 bytes
    0x00000000DF303A95 (File and line number not available): MySQLTrials.exe!std::deque<sql::mysql::MySQL_DebugEnterEvent const * __ptr64,std::allocator<sql::mysql::MySQL_DebugEnterEvent const * __ptr64> >::deque<sql::mysql::MySQL_DebugEnterEvent const * __ptr64,std::allocator<sql::mysql::MySQL_DebugEnterEvent const * __ptr64> > + 0x35 bytes
    0x00000000DF303ACB (File and line number not available): MySQLTrials.exe!std::stack<sql::mysql::MySQL_DebugEnterEvent const * __ptr64,std::deque<sql::mysql::MySQL_DebugEnterEvent const * __ptr64,std::allocator<sql::mysql::MySQL_DebugEnterEvent const * __ptr64> > >::stack<sql::mysql::MySQL_DebugEnterEvent const * __ptr64,std::d + 0x2B bytes
    0x00000000DF302AFE (File and line number not available): MySQLTrials.exe!sql::mysql::MySQL_DebugLogger::MySQL_DebugLogger + 0x3E bytes
    0x00000000DF28CD77 (File and line number not available): MySQLTrials.exe!sql::mysql::MySQL_Connection::MySQL_Connection + 0x227 bytes
    0x00000000DF284184 (File and line number not available): MySQLTrials.exe!sql::mysql::MySQL_Driver::connect + 0xA4 bytes
    e:mysqlmysql.cpp (23): MySQLTrials.exe!wmain + 0x5B bytes
    f:ddvctoolscrt_bldself_64_amd64crtsrccrt0.c (240): MySQLTrials.exe!__tmainCRTStartup + 0x19 bytes
    f:ddvctoolscrt_bldself_64_amd64crtsrccrt0.c (164): MySQLTrials.exe!wmainCRTStartup
    0x00000000C1CF167E (File and line number not available): KERNEL32.DLL!BaseThreadInitThunk + 0x1A bytes
    0x00000000C3EDC3F1 (File and line number not available): ntdll.dll!RtlUserThreadStart + 0x21 bytes

---------- Block 483 at 0x0000000068D93960: 11 bytes ----------
  Leak Hash: 0x1D599652, Count: 1, Total 11 bytes
  Call Stack (TID 4628):
    0x00000000C3EC5630 (File and line number not available): ntdll.dll!RtlAllocateHeap
    f:ddvctoolscrt_bldself_64_amd64crtsrcnewaop.cpp (7): MySQLTrials.exe!operator new[]
    0x00000000DF32199C (File and line number not available): MySQLTrials.exe!sql::mysql::MySQL_ResultBind::bindResult + 0xA0C bytes
    0x00000000DF321379 (File and line number not available): MySQLTrials.exe!sql::mysql::MySQL_ResultBind::bindResult + 0x3E9 bytes
    0x00000000DF313F69 (File and line number not available): MySQLTrials.exe!sql::mysql::MySQL_Prepared_ResultSet::MySQL_Prepared_ResultSet + 0x169 bytes
    0x00000000DF2EC0E1 (File and line number not available): MySQLTrials.exe!sql::mysql::MySQL_Prepared_Statement::executeQuery + 0x1F1 bytes
    e:mysqlmysql.cpp (33): MySQLTrials.exe!wmain + 0x13 bytes
    f:ddvctoolscrt_bldself_64_amd64crtsrccrt0.c (240): MySQLTrials.exe!__tmainCRTStartup + 0x19 bytes
    f:ddvctoolscrt_bldself_64_amd64crtsrccrt0.c (164): MySQLTrials.exe!wmainCRTStartup
    0x00000000C1CF167E (File and line number not available): KERNEL32.DLL!BaseThreadInitThunk + 0x1A bytes
    0x00000000C3EDC3F1 (File and line number not available): ntdll.dll!RtlUserThreadStart + 0x21 bytes

Visual Leak Detector detected 119 memory leaks (640915 bytes).
Largest number used: 697643 bytes.
Total allocations: 837447 bytes.
Visual Leak Detector is now exiting.

问题:

为什么我们在MySQL.cpp(23):行看到泄漏

    pConnection = driver->connect("tcp://127.0.0.1:3306", "username", "password");

和MySQL.cpp(33)

    pResultSet= m_pPreparedStatement->executeQuery();

尽管我们删除了CCD_ 2和CCD_?为什么我们也需要删除pPreparedStatement来释放结果集?

我可以从您的语句中推断出,您已经释放了与以下语句的连接:

delete pConnection

这意味着当你再次初始化PreparedStatement时,你必须像一样初始化它

pPreparedStatement = pConnection->prepareStatement(...)

因此,您不能释放PreparedStatement对象,而只能重用该对象。默认情况下,C++没有垃圾回收,当您不再需要对象时,必须释放它们。

为了接近Java的垃圾收集,我建议您使用shared_ptrscoped_ptr,它们将在不再需要对象时释放内存。

您可以查看Boost C++库,了解如何动态释放内存的全面教程。例如:

boost::scoped_ptr<sql::Connection> con(driver->connect(host, user,pass));
boost::scoped_ptr<sql::Statement> stmt(con->createStatement());

这样,您就不需要记住对对象调用delete,因为当对象不再在作用域中时,内存将被释放。但是你必须包括boost库。

#include <boost/scoped_ptr.hpp>

在我看来,很明显,prepared语句包含用于检索结果集的查询字符串,也就是说,我很清楚,prepared语句位于与结果集不同的内存位置,您也可以从代码中推断出:

sql::ResultSet *pResultSet = NULL;
sql::PreparedStatement *pPreparedStatement = NULL;

因此,当连接超出范围或被释放时,为准备好的语句分配的内存仍然与包含该查询的对象绑定,但由于连接对象被释放,您就无法再访问内存的那部分,而且因为它不像您那样被释放,所以您的操作系统仍然将内存的那一部分标记为正在使用,从而导致存储器泄漏。

为什么要经历这么多麻烦,找出为什么你有空闲的内存来防止内存泄漏,这对我来说似乎很明显,而不是只添加一行代码:

delete pPreparedStatement.

正如随附文档中所描述的,或者我喜欢做的,将它封装在一个类中(例如dbConnection),并让析构函数完成所有工作,这样你只需要在一个位置编写语句,然后就可以忘记它。这是一种简单而干净的实践,它教会了编写干净无泄漏的代码。

通过额外的库添加垃圾收集只会让你变得草率,并导致软件出现缺陷。在我看来,这通常不值得麻烦,而且可能会带来大量开销,而你真的必须为每个对象添加一行代码才能删除。此外,我认为当一些垃圾被删除,而仍然需要它所包含的数据,但垃圾收集来得很早,或者事实上你很快就让你的对象超出了范围,你必须找出为什么你的应用程序在特定情况下不断崩溃时,这种懒惰最终会咬你的屁股。当你自己完成所有的工作时,一开始可能会看起来更麻烦,但最终你会很高兴你可以在几秒钟内纠正这种编码错误,而不是花很长时间寻找错误,结果却发现你的代码中有一个小错误。

选择您正在使用的MySQL版本搜索"Prepared SQL语句语法"。

你会发现3个具体的活动和语法示例A) PREPARE语法B) EXECUTE语法C) DEALLOCATE语法

这可能比删除ResultSet对象更适合您当DEALLOCATE完成时,您可能会看到您的资源已经释放。

最新更新