某個特定的存儲過程在SQL 2008中執行會遇到如下錯誤:
Msg 701, Level 17, State 123, Procedure GetAllRevisions_Monthly, Line 22
There is insufficient system memory in resource pool 'internal' to run this query.
Msg 701, Level 17, State 65, Procedure GetAllRevisions_Monthly, Line 22
There is insufficient system memory in resource pool 'internal' to run this query.sql
咱們能夠從問題的描述和現象中獲得什麼信息:windows
首先咱們須要確認幾個問題:緩存
1. 這個存儲過程在單獨執行的時候會不會遇到這個錯誤。也就是說,在一個沒有其餘用戶訪問的時候,單獨執行這個存儲過程。這一點很是重要,能夠幫助咱們確認SQL Server是因爲整體的工做負載過高而致使沒法分配足夠內存執行語句,仍是這條語句自己執行的問題。服務器
2. 這個存儲過程是否是每次執行都會遇到這個錯誤。session
在這個案例中的情形,該存儲過程即便在單一用戶訪問的時候執行,也會遇到這樣的錯誤,而且在每次執行的時候都會出現一樣的錯。須要注意的是,這個存儲過程在第一次執行的時候,會執行一分鐘左右之後,報出錯誤信息。而當第二次執行和之後屢次執行的時候,都是不到一秒當即報錯。ide
這裏額外的介紹一下對於語句和存儲過程,屢次測試的時候須要注意的問題:任何語句在第一次執行的時候,會生成執行計劃而且將執行計劃緩存的SQL Server的內存中,而第二次或者之後屢次執行,只要以前存放的執行計劃沒有從內存中清除,語句和存儲過程是會重用原有存儲的執行計劃。所以,若是咱們但願每次測試都能實現語句第一次執行時候的效果,咱們須要每次執行以前將緩存的執行計劃手工清除。DBCC freeprocache這個命令能夠幫助咱們清除全部緩存的執行計劃。性能
這個案例的測試狀況以下:測試
Dbcc freeproccacheui
執行存儲過程,用時一分鐘,報錯。this
再次執行存儲過程,馬上返回錯誤。
Dbcc freeproccahe
執行存儲過程,用時一分鐘,報錯。
再次執行存儲過程,馬上返回錯誤。
經過這個現象,能夠得出結論,首次執行一分鐘之後報錯,是由於對該存儲過程進行了編譯和生成執行計劃。然後續執行時當即報錯,是重用了存儲過程緩存的執行計劃。也就是說,這個存儲過程的解析和編譯過程是沒有問題的。可是一旦按照編譯的執行計劃執行的時候,就遇到了內存不足的問題。
以上是咱們經過問題的現象和描述分析出問題的可能性。接下來咱們就進入重現問題收集log的階段。
對於內存不足的錯誤,咱們須要收集哪些信息用以診斷呢?
Windows性能監視器日誌:
SQLServer:Memory
Memory --這個object描述的是windows的內存分配和可用內存狀況
SQL Server 動態性能視圖:
sys.dm_exec_query_memory_grants
sys.dm_os_memory_clerks
dbcc memorystatus
如何收集這些信息?
Windows性能監視器日誌,只須要在windows性能監視器裏面自定義一個用戶收集結果集,而後將objectSQLServer:Memory添加進去就能夠了。須要主要的是設置收集間隔,因爲這個錯誤在語句開始執行後很快就會出現,所以,將收集間隔設置的過大時是難以準確收集到問題出現時的內存異常狀況,咱們建議將收集間隔設置爲1-2秒。
收集SQL Server的動態性能視圖的方法相似,咱們可使用以下腳本每隔一秒打出一次動態性能視圖的結果集合SQL Server的內存狀況:
while(1=1)
begin
print getdate()
print '*****sys.dm_os_memory_clerks******'
select * from sys.dm_os_memory_clerks
print '*****sys.dm_exec_query_memory_grants******'
select * from sys.dm_exec_query_memory_grants
print 'DBCC memorystatus'
dbcc memorystatus
waitfor delay '00:00:01'
end
在management studio中執行這個腳本,這個腳本會每隔一秒打印一次兩個view的信息和dbcc 命令的輸出。咱們建議設置當前的查詢結果輸出的文件的方式來輸出這個腳本的結果。
如何爲這個問題收集信息?
如今咱們已經知道了如何重現這個問題,也知道了對於這個問題應該收集什麼信息來檢查,接下來的問題是,如何收集信息?咱們須要在問題出現以前,先講兩個部分信息收集啓動:啓動咱們本身配置的windows 性能監視器日誌,啓動腳本。接下來,咱們能夠在management studio裏面執行語句而且重現問題了。
信息的分析
1. 問題出現以前,腳本打出來的dbcc memorystatus:
Current Buffer Pool Stats
-----------------------------------
Total Buffers=1048576 (8192MB)
Max Committed=640000 (5000MB)
Committed Target=640000 (5000MB)
Committed=41344 (323MB)
Hashed=6255 (48MB)
Free=17746 (138MB)
Stolen=17343 (135MB)
Reserved=590656 (4614MB)
OutOfMemory=false
WaitingForRM=false
Failed to complete calculation of statistics!
Latched=0 (0MB)
Dirty=0 (0MB)
In IO=0 (0MB)
Stolen potential=1 (0MB)
這個信息如何閱讀?committed target表示SQL Server能夠爲buffer pool分配的空間,64000是以8k的page爲單位的,所以640000計算出來時5000MB。Committed表示當前已經使用的buffer pool的大小,41344計算出來時323MB。這個信息告訴咱們,在問題出現以前,該系統的buffer pool裏面是還有超過4677MB的空閒內存。
2. Windows event log:
在診斷內存問題的時候,首先須要確認的就是,該內存問題是因爲windows的內存缺少致使的仍是SQL Server自身的內存問題致使的。當windows遇到內存壓力,沒有可用內存的時候,OS被強制在其上運行的全部應用程序釋放物理內存。SQL Server在這種狀況下,因爲buffer pool短期以內須要釋放大量內存而且急劇縮小大小,當時在SQL Server上執行的語句也會報出錯誤701,沒有足夠的內存執行語句。可是這個內存錯誤的自己是因爲windows 強制收回內存致使的。咱們會在另外一個案例中詳細討論這個問題。
咱們檢查OS的可用內存,memory:available memory(MB),在這個案例中,問題發生以前和問題發生的時候,windows依然保持5.7GB的內存。因此這裏的內存問題是SQL Server內部產生的,並非操做系統和服務器的內存缺少問題。
接下來咱們檢查SQLServer:Memory 下面的Granted workspace memory(KB),這個值代辦SQL Server分配出來執行語句的內存,主要是用來作排序,哈希鏈接等等。
經過這個event,咱們發現這裏有兩次巨大的內存分配(咱們測試的時候執行了兩次存儲過程),這裏的最大值爲4617MB。基本上接近了buffer pool中全部的空閒內存。
咱們比較在腳本中收集到的sys.dm_exec_query_memory_grants的信息:
session_id dop request_time grant_time requested_memory_kb granted_memory_kb required_memory_kb
---------- --- ------------------------ ------------------------ ------------------- ----------------- ------------------
53 16 02/19/2010 17:13:02.540 02/19/2010 17:13:02.540 4728392 4728392 4728392
分配的數值和時間上徹底吻合。
若是咱們遇到的問題是,發現有大量的內存分配給了用戶session,可是咱們不知道用戶session在當時執行的語句是什麼,如何經過動態性能視圖來定位語句呢?在sys.dm_exec_query_memory_grants中包含了一個sql_handle的列,咱們能夠經過這個列去鏈接:
Select *,text, query_plan from FROM sys.dm_exec_query_memory_grants
CROSS APPLY sys.dm_exec_sql_text(sql_handle)
CROSS APPLY sys.dm_exec_query_plan (plan_handle)
這樣咱們就能夠直接經過動態性能視圖獲得了SQL語句和該SQL語句使用的XML格式的執行計劃了。咱們將XML格式的執行計劃另存爲.sqlplan的後綴的文件,而後在management studio中打開,能夠將其展開爲圖形界面的執行計劃。若是不熟悉使用XML格式的執行計劃,這種方法能夠簡化檢查執行計劃的工做。
問題分析到這裏,基本上咱們就能夠定位出緣由了,確實是由於這個存儲過程執行時,SQL Server預估的內存過大,致使現有內存不足以支持存儲過程的執行。
解決的方法:
接下來,咱們就能夠查看XML的執行計劃,在這裏,咱們發現SQL Server對這個存儲過程使用的並行度爲16的執行方式。對於使用並行的語句,SQL Server面對的兩個額外的開銷:
a. 更多的內存分配
b. 更多的CPU資源消耗。
所以,一旦咱們確認內存使用過多或者CPU使用率太高是跟並行執行有關,咱們須要對SQLServer手工設置一個較低的最大並行度參數:
sp_configure 'show advanced options',1 reconfigure with override
go
sp_configure 'max degree of parallelism',4 reconfigure with override
上述命令中第一條是用來打開高級選項的,第二條是將最大並行度設置爲4.以上設置不須要重啓SQL Server服務。關於最大並行度的參數,咱們建議設置爲CPU數或者CPU數的一半。在多於32個CPU的系統中,咱們建議設置這個值爲4或者8.
額外的問題:
到目前爲止,緣由已經很是清楚了,並且咱們獲得的解決問題的方法。這裏有一個疑問,爲何SQL Server會在明知道沒有這麼多可用內存的狀況下,去生成一個須要這麼多內存的執行計劃呢?而且還不斷的重用這個不能執行的執行計劃?這樣是SQL Server的產品設計有問題嗎?
其實這個問題的出現是一個很巧合的狀況。咱們根據SQL Server的dump來分析,發現這個語句在評估和生成執行計劃的時候,計算出來所須要的內存接近4670MB,當時buffer pool裏面還有4677MB的內存,因此評估出來的執行計劃雖然接近了零界值,可是依然是可以知足執行計劃的須要的。而當SQL語句真正按照執行計劃執行的時候,其實還有一些少許的內存的額外開銷,正是由於加上了這些額外的開銷,終於超出了剩餘的4677MB的內存限制。在這種狀況下,SQL Server每次對執行計劃的評估和緩存都成功了,而執行的時候纔會報錯。這也就是爲何下次執行的時候該存儲過程仍是會重用緩存的執行計劃。當語句到執行階段的時候,無論成功不成功,都不會回頭改寫緩存的執行計劃,因此SQL server就不斷的爲這個存儲過程重用一樣的執行計劃,直到咱們將最大並行度降爲4,SQL Server生成新的並行度爲4的執行計劃之後,該存儲過程就能正確的運行了。