SQL Server內存泄漏
翻譯自:https://mssqlwiki.com/2012/12/04/sql-server-memory-leak/
什麼是內存泄漏?
當一個進程分配了內存,它應該回收並釋放給操作系統。如果由於代碼裏的缺陷沒有回收內存,被稱爲泄漏,它會導致操作系統和應用程序的內存壓力。
有關SQL Server內存泄漏的神話
SQL Server內存管理設計爲基於系統可用內存數量和SQL Server裏Max server memory設置來動態增加和收縮內存。
很多次,系統管理員查看SQL Server內存使用,如果他們發現SQL Server內存使用很高會認爲SQL Server內存泄漏了。
這是不對的,SQL Server是基於服務的應用程序,它的內存管理器設計爲按需(除了大頁)增加內存使用,除非Windows發出低內存提醒,否則不會收縮它的內存使用。我們可以在SQL Server裏通過配置Max server memory來控制SQL Server內存使用。這個配置限制了SQL Server Bpool的使用,而不會控制整個SQL Server內存使用。分配在BPOOL以外(也叫MTL或MTR)的SQL Server內存部分,我們沒有辦法控制在bpool外SQL Server可以使用多少內存,但是non-bpool內存使用通常很低,可以很容易通過研究運行在SQL Server裏的組件來預估。
例如:如果你想SQL Server在服務器上只使用10GB內存。考慮在Bpool以外SQL Server可能需要多少內存,並相應的設置“Max server memory”。在這裏,如果你預估在Bpool以外SQL Server使用1.5GB,那設置Max server memory爲8.5GB。
什麼可以導致SQL Server內存泄漏?
SQL Server代碼有一個邏輯去分配內存,但是不會回收它。如果在SQL Server裏有任何組件導致了內存泄漏,它可以很容易使用像sys.dm_os_memory_allocation、sys.dm_os_memory_clerks和sys.dm_os_memory_objects等識別出來。但是,SQL Server裏大多數的內存泄漏時由加載到SQL Server進程裏的第三方DLL導致的。
注意:加載到SQL Server裏的非SQL Server DLL的所有內存分配將會產生“Mem to Leave”(在Bpool之外),並且他們被稱爲直接Windows分配(DWA)。
當在SQL Server裏有內存溢出條件,如果你猜測有內存泄漏。首先確定誰在消耗內存。如果SQL Server沒有使用在MemToLeave裏的大多數內存,並且你仍然獲得Mem to Leave,有可能存在內存泄漏,它由加載到SQL Server裏某個DLL導致。參考第1部分(MTL錯誤)https://mssqlwiki.com/sqlwiki/sql-performance/troubleshooting-sql-server-memory/
以下查詢可以用於確定SQL Server在MTL裏的實際內存消耗。
select sum(multi_pages_kb) from sys.dm_os_memory_clerks
如果SQL Server的內存消耗非常低,並且你仍然看到像下面的SQL Server內存錯誤,請重點注意泄漏問題。
例如:
SQL Server 2000 WARNING: Failed to reserve contiguous memory of Size= 65536. WARNING: Clearing procedure cache to free contiguous memory. Error: 17802 “Could not create server event thread.” SQL Server could not spawn process_loginread thread. SQL Server 2005/2008 Failed Virtual Allocate Bytes: FAIL_VIRTUAL_RESERVE 122880
如何識別內存泄漏和故障排除?
在Windows裏有多種方法識別誰在進程裏泄漏內存。在這邊博文裏,我們將討論如何使用1.Windows debugger、2.Debug diagnostics tools for windows和3.UMDH識別內存泄漏。
讓我們創建一個示例DLL加載到SQL Server進程裏泄漏內存,看看如何使用以上提到的工具排除泄漏故障。
從這個鏈接(http://sdrv.ms/TH1qfR)下載HeapLeak.dll,並安裝32位(http://www.microsoft.com/en-us/download/details.aspx?id=5555)或64位(http://www.microsoft.com/en-us/download/details.aspx?id=14632)的Microsoft Wisual C++ 2010 Redistributable Package來讓這個DLL運行。
-- Create an extended stored procedure in SQL Server exec sp_addextendedproc 'HeapLeak','C:\HeapLeakdll\HeapLeak.dll' -- Let us execute this Extended SP 30 times and leak memory. exec HeapLeak go 30
我們也將在SQL Server裏啓用以下跟蹤標誌,當有內存溢出錯誤時自動產生過濾dump並查看如何識別誰在泄漏內存。
-- 2551 is used to enable filter dump. dbcc traceon (2551,-1) go -- 8004 is used to take memory dump on first occurrence of OOM condition dbcc traceon (8004,-1) go -- Note: Both the trace flags listed above are un-documented, So use it at your own risk and there is no guarantee that this trace flags will work in future versions of SQL Server
當我們啓用跟蹤標誌,我們在SQL Server裏觸發內存溢出錯誤而產生OOM內存dump。通過執行以上的擴展存儲過程30次,我們從MTL泄漏了大約300MB內存。
讓我們執行以下創建XML句柄的腳本。XML句柄從MTL的內存分配將不久獲得內存溢出錯誤,因爲我們執行的擴展存儲過程已經泄漏了內存。
(不要在沒有執行HeapLeak就直接運行以下XML腳本。以下導致OOM錯誤的腳本由於對每個執行創建的句柄,但是它佔用SQL Server分配,所以不會幫助我們來理解如何排除由第三方DLL泄漏導致的故障)
注意:
1. SQL Server內存dump將會在SQL Server錯誤日誌目錄下生成。
2. MTL的大小在32位SQL Server是256MB+Max worker threads *0.5,所以大約是384MB除非你使用-g開關修改。
DECLARE @idoc int DECLARE @doc varchar(1000) SET @doc ='<ROOT> <Customer CustomerID="VINET" ContactName="Paul Henriot"> <Order CustomerID="VINET" EmployeeID="5" OrderDate="1996-07-04T00:00:00"> <OrderDetail OrderID="10248" ProductID="11" Quantity="12"/> <OrderDetail OrderID="10248" ProductID="42" Quantity="10"/> </Order> </Customer> <Customer CustomerID="LILAS" ContactName="Carlos Gonzlez"> <Order CustomerID="LILAS" EmployeeID="3" OrderDate="1996-08-16T00:00:00"> <OrderDetail OrderID="10283" ProductID="72" Quantity="3"/> </Order> </Customer> </ROOT>' EXEC sp_xml_preparedocument @idoc OUTPUT, @doc go 10000
在執行幾次之後,我們會收到以下錯誤。
Msg 6624, Level 16, State 12, Procedure sp_xml_preparedocument, Line 1 XML document could not be created because server memory is low.
從http://msdl.microsoft.com/download/symbols/debuggers/dbg_x86_6.11.1.404.msi下載和安裝Windows Debugger來分析這個dump。
步驟1:(加載這個內存dump文件來排除故障)
打開Windbg。選擇“File”菜單,選擇“Open crash dump”,選擇這個dump文件(SQLDump000#.mdmp)
注意:當你得到異常或斷言,在你的SQL Server錯誤日誌裏將會發現SQLDump000#.mdmp。
步驟2:(設置符號目錄爲Microsoft symbols server)
在命令窗口輸入
.sympath srv*c:\\Websymbols*http://msdl.microsoft.com/download/symbols;
步驟3:(從Microsoft symbols server加載符號)
輸入.reload /f並點擊回車。這將強制debugger立即加載所有符號。
步驟4:(檢查是否符號已加載)
使用debugger命令lmvm驗證是否符號加載到SQL Server。
:028> lmvm sqlservr
start end module name
01000000 02ba8000 sqlservr (pdb symbols) c:\websymbols\sqlservr.pdb\93AACB610C614E1EBAB0FFB42031691D2\sqlservr.pdb
Loaded symbol image file: sqlservr.exe
Mapped memory image file: C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn\sqlservr.exe
Image path: C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn\sqlservr.exe
Image name: sqlservr.exe
Timestamp: Fri Oct 14 15:35:29 2005 (434F82E9)
CheckSum: 01B73B9B
ImageSize: 01BA8000
File version: 2005.90.1399.0
Product version: 9.0.1399.0
File flags: 0 (Mask 3F)
File OS: 40000 NT Base
File type: 1.0 App
File date: 00000000.00000000
Translations: 0409.04e4
CompanyName: Microsoft Corporation
ProductName: Microsoft SQL Server
InternalName: SQLSERVR
OriginalFilename: SQLSERVR.EXE
ProductVersion: 9.00.1399.06
FileVersion: 2005.090.1399.00
FileDescription: SQL Server Windows NT
LegalCopyright: Microsoft Corp. All rights reserved.
LegalTrademarks: Microsoft is a registered trademark of Microsoft Corporation. Windows(TM) is a trademark of Microsoft Corporation
Comments: NT INTEL X86
步驟5:(!address來限制內存信息)
使用!address命令來限制dump進程的內存信息。
0:028> !address -summary
——————– Usage SUMMARY ————————–
TotSize ( KB) Pct(Tots) Pct(Busy) Usage
686a7000 ( 1710748) : 81.58% 81.80% : RegionUsageIsVAD
579000 ( 5604) : 00.27% 00.00% : RegionUsageFree
4239000 ( 67812) : 03.23% 03.24% : RegionUsageImage
ea6000 ( 15000) : 00.72% 00.72% : RegionUsageStack
1e000 ( 120) : 00.01% 00.01% : RegionUsageTeb
122d0000 ( 297792) : 14.20% 14.24% : RegionUsageHeap
0 ( 0) : 00.00% 00.00% : RegionUsagePageHeap
1000 ( 4) : 00.00% 00.00% : RegionUsagePeb
1000 ( 4) : 00.00% 00.00% : RegionUsageProcessParametrs
1000 ( 4) : 00.00% 00.00% : RegionUsageEnvironmentBlock
Tot: 7fff0000 (2097088 KB) Busy: 7fa77000 (2091484 KB)
——————– Type SUMMARY ————————–
TotSize ( KB) Pct(Tots) Usage
579000 ( 5604) : 00.27% : <free>
4239000 ( 67812) : 03.23% : MEM_IMAGE
5fc000 ( 6128) : 00.29% : MEM_MAPPED
7b242000 ( 2017544) : 96.21% : MEM_PRIVATE
——————– State SUMMARY ————————–
TotSize ( KB) Pct(Tots) Usage
1b7bd000 ( 450292) : 21.47% : MEM_COMMIT
579000 ( 5604) : 00.27% : MEM_FREE
642ba000 ( 1641192) : 78.26% : MEM_RESERVE
Largest free region: Base 00000000 – Size 00010000 (64 KB)
查看RegionUsageHeap,它大約是297792KB,最大的空閒區域只有64KB。我們知道SQL Server不廣泛使用堆內存,因此通常SQL Server堆內存分配將不會超過幾MB。在這裏消耗大約290MB,因此使用MTL的其他組件很容易失敗。。
讓我們嘗試理解爲什麼堆內存大約297792KB,嘗試識別是否有一個模型。
步驟6:(讓我們使用!heap -s來顯示堆內存摘要信息)
0:028> !heap -s
LFH Key : 0x672ddb11
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
—————————————————————————–
000d0000 00000002 1024 896 896 6 1 1 0 0 L
001d0000 00008000 64 12 12 10 1 1 0 0
002c0000 00001002 1088 96 96 2 1 1 0 0 L
002e0000 00001002 64 52 52 3 2 1 0 0 L
007c0000 00001002 64 64 64 56 1 0 0 0 L
00d10000 00001002 256 24 24 8 1 1 0 0 L
340b0000 00001002 64 28 28 1 0 1 0 0 L
340c0000 00041002 256 12 12 4 1 1 0 0 L
342a0000 00000002 1024 24 24 3 1 1 0 0 L
34440000 00001002 64 48 48 40 2 1 0 0 L
61cd0000 00011002 256 12 12 4 1 1 0 0 L
61d10000 00001002 64 16 16 7 1 1 0 0 L
61d20000 00001002 64 12 12 4 1 1 0 0 L
62a90000 00001002 1024 1024 1024 1016 2 0 0 0 L
62b90000 00001002 1024 1024 1024 1016 2 0 0 0 L
62c90000 00001002 256 40 40 7 1 1 0 0 LFH
00770000 00001002 64 16 16 2 2 1 0 0 L
63820000 00001002 64 24 24 3 1 1 0 0 L
63830000 00001001 10240 10240 10240 160 21 0 0 bad
64230000 00001001 10240 10240 10240 160 21 0 0 bad
64c30000 00001001 10240 10240 10240 160 21 0 0 bad
65630000 00001001 10240 10240 10240 160 21 0 0 bad
66030000 00001001 10240 10240 10240 160 21 0 0 bad
66a30000 00001001 10240 10240 10240 160 21 0 0 bad
67430000 00001001 10240 10240 10240 160 21 0 0 bad
68130000 00001001 10240 10240 10240 160 21 0 0 bad
68b30000 00001001 10240 10240 10240 160 21 0 0 bad
69530000 00001001 10240 10240 10240 160 21 0 0 bad
69f30000 00001001 10240 10240 10240 160 21 0 0 bad
6a930000 00001001 10240 10240 10240 160 21 0 0 bad
6b330000 00001001 10240 10240 10240 160 21 0 0 bad
6bd30000 00001001 10240 10240 10240 160 21 0 0 bad
6c730000 00001001 10240 10240 10240 160 21 0 0 bad
6d130000 00001001 10240 10240 10240 160 21 0 0 bad
6db30000 00001001 10240 10240 10240 160 21 0 0 bad
6e530000 00001001 10240 10240 10240 160 21 0 0 bad
6ef30000 00001001 10240 10240 10240 160 21 0 0 bad
6f930000 00001001 10240 10240 10240 160 21 0 0 bad
70330000 00001001 10240 10240 10240 160 21 0 0 bad
70d30000 00001001 10240 10240 10240 160 21 0 0 bad
7a160000 00001001 10240 10240 10240 160 21 0 0 bad
7ab60000 00001001 10240 10240 10240 160 21 0 0 bad
7b560000 00001001 10240 10240 10240 160 21 0 0 bad
7d0d0000 00001001 10240 10240 10240 160 21 0 0 bad
7e030000 00001001 10240 10240 10240 160 21 0 0 bad
7ea30000 00001001 10240 10240 10240 160 21 0 0 bad
67f90000 00001003 256 16 16 14 1 1 0 bad
71850000 00001003 256 4 4 2 1 1 0 bad
71890000 00001003 256 4 4 2 1 1 0 bad
67fd0000 00001002 64 16 16 4 1 1 0 0 L
718d0000 00001003 256 40 40 3 1 1 0 bad
71910000 00001003 256 4 4 2 1 1 0 bad
71950000 00001003 256 4 4 2 1 1 0 bad
71990000 00001003 256 4 4 2 1 1 0 bad
67ff0000 00001002 64 16 16 4 1 1 0 0 L
719d0000 00001003 1792 1352 1352 5 2 1 0 bad
71a10000 00001003 256 4 4 2 1 1 0 bad
71a50000 00001003 256 4 4 2 1 1 0 bad
71a90000 00001002 64 16 16 1 0 1 0 0 L
—————————————————————————–
如果你看以上輸出,你可以清晰識別一個模型。創建了多個,每個是10MB。但是如何識別實際上是誰創建了它們?
步驟7:
讓我們取出其中一個10MB的堆內存,並使用帶-h參數的!heap顯示在這個10MB堆內存裏所有的內存分配條目。
我獲取的堆內存是63830000。
0:028> !heap -h 63830000
Index Address Name Debugging options enabled
19: 63830000
Segment at 63830000 to 64230000 (00a00000 bytes committed)
Flags: 00001001
ForceFlags: 00000001
Granularity: 8 bytes
Segment Reserve: 00100000
Segment Commit: 00002000
DeCommit Block Thres: 00000200
DeCommit Total Thres: 00002000
Total Free Size: 00005048
Max. Allocation Size: 7ffdefff
Lock Variable at: 00000000
Next TagIndex: 0000
Maximum TagIndex: 0000
Tag Entries: 00000000
PsuedoTag Entries: 00000000
Virtual Alloc List: 63830050
UCR FreeList: 63830588
FreeList Usage: 00000000 00000000 00000000 00000000
FreeList[ 00 ] at 63830178: 6422de88 . 638ad7e0 Unable to read nt!_HEAP_FREE_ENTRY structure at 638ad7e0
(1 block )
Heap entries for Segment00 in Heap 63830000
63830608: 00608 . 00040 [01] – busy (40)
63830648: 00040 . 02808 [01] – busy (2800)
641b6698: 02808 . 02808 [01] – busy (2800)
……………………………………
……………………………………
……………………………………
……………………………………
步驟8:(讓我們截取分配的堆內存條目之一,並嘗試識別在它裏面是什麼)
0:028> db 641b6698
641b6698 01 05 01 05 93 01 08 00-49 61 6d 20 66 69 6c 69 ……..Iam fili
641b66a8 6e 67 20 74 68 65 20 68-65 61 70 20 66 6f 72 20 ng the heap for
641b66b8 64 65 6d 6f 20 61 74 20-4d 53 53 51 4c 57 49 4b demo at MSSQLWIK
641b66c8 49 2e 43 4f 4d 00 00 00-00 00 00 00 00 00 00 00 I.COM………..
641b66d8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 …………….
641b66e8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 …………….
641b66f8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 …………….
641b6708 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 …………….
0:028> db 63830648
63830648 01 05 08 00 89 01 08 00-49 61 6d 20 66 69 6c 69 ……..Iam fili
63830658 6e 67 20 74 68 65 20 68-65 61 70 20 66 6f 72 20 ng the heap for
63830668 64 65 6d 6f 20 61 74 20-4d 53 53 51 4c 57 49 4b demo at MSSQLWIK
63830678 49 2e 43 4f 4d 00 00 00-00 00 00 00 00 00 00 00 I.COM………..
63830688 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 …………….
63830698 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 …………….
638306a8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 …………….
638306b8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 …………….
類似的,你可以dump多個堆分配來識別一個模型。
現在如果你查看dump的內存,你會看到一個字符串可能對你識別創建堆內存的DLL有幫助。在以上堆裏有一個模型。所有的堆分配都有以下字符串“I am filing the heap for demo at MSSQLWIKI.COM”
注意:你可以使用L大小dump更多內存,使用db或dc命令的示例:db 63830648 L1500
步驟9:
讓我們使用notepad打開加載到SQL Server裏用於測試的DLL,查看是否有一個字符串匹配這個模型。
是的,證明這個DLL導致了泄漏。在實踐中你可能得去驗證不同的堆分配來識別這個模型。
這是一個在泄漏實際發生之後從內存dump找到泄漏。找到一個模型並識別分配該內存的模塊並不總是容易的,在這裏你可以使用像debug diagnostic tool、UMDH等工具來跟蹤泄漏。在我的下一篇博文中,我將發表如何使用Debug diagnostics tools跟蹤內存泄漏。https://mssqlwiki.com/2012/12/06/debugging-memory-leaks-using-debug-diagnostic-tool/