關於 ADO 記憶體洩露 記憶體增長 的一些個人觀點及解決方案
當我們開發一個ADO客戶端程式時,經常會發現程式執行以後,其擁有(佔用)的虛擬記憶體大小不斷增加,在一般情況下我們並不希望這樣的事情發生,因此我們需要進行一些額外的配置。
首先,先來分析一下記憶體增長的原因。記憶體增長的可能的原因有兩點:
一是由客戶端程式的記憶體使用或管理不正確導致的記憶體洩露,在編寫客戶端程式時,申請記憶體之後沒有對應的釋放語句。很多初學者沒有認識到或弄清楚堆(stack)和棧(heap)的區別而將堆疊的概念混淆,不知道哪些變數儲存在堆空間,哪些儲存在在棧空間,哪些儲存在在全域性靜態空間——區域性變數存放在棧空間不用也無法釋放記憶體,全域性或靜態變數存放在程式碼之中同樣不可以釋放記憶體,而堆空間常常是使用者特意或非特意申請的,用後必須釋放而且釋放的時機要恰到好處並且與其相關的指標要清空——堆疊概念的模糊在編寫非託管程式時可能會發展成為一個可怕的問題,比如說編寫C程式尤其是C++程式時,我就曾經在呼叫CStatic::SetBitmap(HBITMAP)的時候沒有接受返回的HBITMAP而導致了一個隱式的記憶體洩露,這類問題常常很難發覺。
二是由服務提供者(資料庫驅動程式)產生的記憶體增長,這通常情況下被誤認為是記憶體洩露,實際上任何一個成熟的資料庫驅動程式一定是經過縝密設計的,產生記憶體洩露的可能性很小,記憶體的增長常常是為了提高效能而由設計者故意設計的。比如說在使用Access資料庫(.mdb)的時候就會用到Jet引擎,每一個用到Jet引擎的程式都會載入一個msjet40.dll的動態連結庫,這個檔案就是Jet引擎。通過對其彙編程式碼的初步跟蹤,發現如果使用者程式設計得當(沒有記憶體洩露,且對於使用ADO DB的使用者程式來說,Connection和RecordSet都只有一次或少量的CreatInstance(),RecordSet每Open一次都對應一次Close),那麼在查詢記錄時,只要不進行GetChunk或AppendChunk,Jet引擎就會重複利用以往記憶體而不會申請新的記憶體;反之,在進行GetChunk或AppendChunk的操作時,由於資料量巨大Jet引擎就會在重複利用記憶體之後產生新的記憶體需求,接著就會申請新的所需大小的記憶體,在GetChunk以後即使呼叫RecordSet的Close這些記憶體也不會釋放,以便再次GetChunk的時候會提高存取速度。
其次,接著研究解決方法。在ADO的各個方法之中,我並沒有找到任何可以釋放這些記憶體頁的函式(也許是不太瞭解)。我認為既然記憶體頁是由服務提供者申請並管理的,而ADO繼承於OLE DB需要透過OLE DB來訪問Jet引擎,ADO或OLE DB也都算是一個客戶端程式,只要服務提供者沒有提供介面函式,那麼它就無權干涉由服務提供者申請並管理的記憶體。因此,我們將重點放在Jet引擎的學習上。
有一些書目值得我們學習。
Visual C++ 隨帶的文件應是關於 MFC 實現 DAO 的第一個資訊來源。不僅應該查閱 MFC DAO 示例和 MFC 技術說明,還應查閱 Class Library Reference
有關 DAO 工作方式的其它資訊,請查閱 Microsoft Jet Database Engine Programmer's Guide(Microsoft Press 出版)。
另外還有兩篇推薦文章,均位於 Microsoft Developer Network 光碟上,它們是:
"Jet Database Engine ODBC Connectivity",作者:Black、Neil 和 Stephen Hecht。
這篇文章詳細講述了 Microsoft Jet 如何使用 ODBC 檢索伺服器資料。對於所有用 DAO 編寫重要伺服器應用程式的人,這都不失為一篇必讀文章。
"ODBC:Architecture,Performance,and Tuning",作者:Lambert、Colleen。
本白皮書提供對 ODBC 工作方式的良好概述,並以現實、有用的方式講述了效能問題。
最後,提供一個解決方案。終於找到一份微軟的說明,還算與我們的主題相關(在最後有部分摘抄),意思就是說在登錄檔中HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\3.5\Engines\Jet 3.5設定一個MaxBufferSize的DWORD為200h即512就搞定。
可以參考如下程式碼:
_ConnectionPtr m_pConn;//_ConnectionPtr物件
if ( m_pConn == NULL ) {//建立連線
try{
m_pConn.CreateInstance("ADODB.Connection");//建立一次即可
m_pConn->ConnectionTimeout=3;//連線嘗試時間
m_pConn->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=*****.mdb;"
"Jet OLEDB:Database Password=*****","","",adModeUnknown);//連線Access資料庫
m_pConn->GetProperties()->GetItem(_variant_t("Jet OLEDB:Max Buffer Size"))->PutValue(_variant_t((long)512,VT_I4));//jet4.0會將輸入修正到0x100~0x10000之間
m_pConn->GetProperties()->GetItem(_variant_t("Jet OLEDB:Recycle Long-Valued Pages"))->PutValue(_variant_t((short)-1,VT_BOOL));
}catch(_com_error &e){//捕捉異常
AfxMessageBox(e.Description());
::ExitProcess(-1);
}
}
另外可以搜尋"Jet OLEDB:Max Buffer Size","DBPROP_JETOLEDB_MAXBUFFERSIZE"。
總之,即使在做了這些工作之後,Jet的部分虛存(堆空間)仍然會隨著每一次查詢一點一點地緩慢增加(Connection的日誌?),這時唯一的辦法似乎就是每隔一段時間關閉一次資料庫,幾秒鐘之後再重新連線。另外,利用一些類似Valgrind的軟體進行檢測,或者利用軟體除錯,在VirtualAllocEx處下斷點,再檢視呼叫堆疊,分析來源以試圖解決。
PS:Jet引擎微軟好像要放棄了,也不知道又有什麼新花樣,無論出什麼新東西,痛苦的都是程式設計師。最後附上工作記錄,也不算白忙活。
———————————————————————切割機———————————————————————
INF: Jet 3.0 Dynamic Memory Usage and Access ODBC Driver
This article was previously published under Q154384 Many enhancements have been made to Microsoft Jet 3.0 over the Jet 2.x versions to improve performance.Dynamic Memory Usage is one of them and it is a configurable performance setting.Microsoft Jet 2.x pre-allocated a default of 512 KB for its buffer size with an upper limit (MaxBufferSize) of 4 MB RAM.The behavior of MaxBufferSize has been changed in Jet 3.0, which allocates memory on an "as- needed" basis up to an internally calculated high water mark.The idea is to efficiently use the memory in large RAM systems without the need to adjust Registry Settings.The high water mark (MaxBufferSize) for Jet 3.0 is calculated by the following formula: ( (((Total Ram in MB - 12)/4 ) * 1024) + 512 )KB For example, for a system with 32 MB RAM, Jet 3.0 uses a calculated MaxBufferSize of 5,632 KB.You can override this value by setting the MaxBufferSize value in the following HKEY_LOCAL_MACHINE subtree: \SOFTWARE\Microsoft\Jet\3.0\Engines\Jet For Jet 3.5, the registry location for MaxBufferSize is: \SOFTWARE\Microsoft\Jet\3.5\Engines\Jet 3.5 In addition, Jet 3.5 settings may be modified at the ODBC data source level.For additional information, please see the following article in the Microsoft Knowledge Base: 168686PRB: Performance Loss When Upgrading Jet ODBC Driver For MaxBufferSize (DWORD), enter a value in KB.You may have to add the keys, if you do not have them already in the registry.The minimum value that Microsoft Jet 3.0 can use by default is 512 KB.However,the minimum value the the user can set is 128 KB.Unlike Jet 2.x, Jet 3.0 can exceed the MaxBufferSize.When this occurs, the engine starts up a background thread to start flushing pages to bring the buffer pool down to the designated MaxBufferSize.For more information on Jet 3.0, refer to the "Microsoft Jet Database Engine Programmer's Guide" published by Microsoft Press.Question:How does MaxBufferSize setting affect ODBC applications using the Access Driver?Answer:Microsoft Access ODBC Driver v3.x is based on Jet 3.0 Engine.If you do not set the MaxBufferSize in the Registry and try to insert records continuously into a table, you will notice a huge growth in the memory consumed by the application (process) using the driver.If your application, running on Windows NT, is inserting records without ever closing the database connection, the system may run out of virtual memory.The memory growth will eventually reach a peak value that is equal to the high water mark computed by Jet 3.0 and can be easily mistaken for a memory leak in the driver.However, it is important to note that it is not a memory leak as the memory growth stabilizes after reaching the peak value.This behavior is by design.If you don't want the driver/Jet to allocate dynamic memory based on the formula discussed above, it is recommended that you set the MaxBufferSize to a value like 512 KB.You can set it to a higher value to gain better performance. Article ID: 154384 - Last Review: August 25, 1999 - Revision: 1.0APPLIES TO
- Microsoft Open Database Connectivity 3.0
- Microsoft Open Database Connectivity 3.5
Keywords: |
KB154384 |
PRB: Performance Loss When Upgrading Jet ODBC Driver
System TipThis article applies to a different version of Windows than the one you are using. Content in this article may not be relevant to you. Visit the Windows XP Solution Center This article was previously published under Q168686 A Microsoft Visual C++ database project that uses the Microsoft Access 97 (Jet 3.5) ODBC Driver has a noticeable drop in performance compared to similar projects that use the Microsoft Access 95 (Jet 3.0) ODBC Driver.CAUSE
The Microsoft Access 97 ODBC driver correctly reads and uses the MaxBufferSize performance setting in the ODBC data source.For applications that deal with large amounts of database activity, the default MaxBufferSize setting of 512K may be too small and result in a performance loss.The default MaxBufferSize setting of 512 was designed for optimal performance when using Jet databases under normal conditions but will restrict the amount of memory allotted to the driver and impact performance when using the driver more aggressively. Increase the MaxBufferSize setting to reduce the amount of swapping that is caused by the more restrictive high watermark of 512K.The MaxBufferSize setting can be pre-set in the ODBC datasource or set programmatically starting in Visual C++ version 5.0 using the SetOption method documented in the DAO SDK.Increasing the MaxBufferSize setting greatly improves performance when the database activity level is high by preventing frequent page swapping.Setting MaxBufferSize to 0 activates Jet's built-in mechanism to determine the high watermark.Please look at the MORE INFORMATION section below for details. The Microsoft Access 95 ODBC driver distributed with Microsoft Visual C++ 4.x and Microsoft Office 95 (version 3.00.xxxx) ignores the MaxBufferSize setting in the ODBC data source.Instead, it uses the Jet engine's default setting, which is determined based on the amount of physical RAM on the computer using the following formula:((Total Physical RAM in MB - 12 MB) / 4) + 512 KB
For a computer with 32 MB of RAM:
((32 MB - 12 MB / 4) + 512 KB = 5632 KB
So when you use this driver, the MaxBufferSize setting is actually well over the default 512 KB in the data source.
The Microsoft Access ODBC driver distributed with Visual C++ 5.0 and Microsoft Office 97 (version 3.50.xxxx) correctly reads and uses the 512 KB default setting from the ODBC data source.This means that the 3.50.xxxx driver is going to use a considerably smaller buffer than the 3.00.xxxx driver when used with the default settings.The impact on performance shows up after the driver has used up the 512 KB of memory and starts a cleanup thread to flush out the oldest buffer pages.If you are submitting a large amount of database requests (constantly inserting records, running queries or leaving open a large number of recordsets), the cleanup thread will eventually not manage to keep up with the amount of page swapping that has to happen.As a result, Jet begins to react more slowly to additional requests.
To change the MaxBufferSize setting manually, double-click your Access database DSN in the ODBC Administrator.In the data source setup dialog box, click the "Options>>>" button to display the "Buffer Size" edit box and type in the new desired MaxBufferSize value there. For additional information on Jet performance settings such as MaxBufferSize, see Chapter 13 of the Microsoft Jet Database Engine Programmer's Guide (Microsoft Press).
For information on setting the MaxBufferSize setting programmatically, search for the "SetOption Method" in the DAO SDK online help in Visual Studio 97.