windbg分析一次大查詢導致的記憶體暴漲

Invokerr發表於2019-06-15

  專案上反饋了一個問題,就是在生產環境上,使用者正常使用的過程中,出現了伺服器記憶體突然暴漲,客戶有點慌,想找下原因。

  講道理,記憶體如果是緩慢上漲一直不釋放的話,應該是存在記憶體洩漏的,這種排查起來比較困難,還得找開發一塊看;但像這種突然暴漲的,肯定是把某些大物件放到記憶體裡了,而最有可能的,就是大查詢了,比如把幾百萬資料查出來這種,但這種一般等使用者用完這個功能記憶體就會降下來。

  環境:IIS+.net framework。發現是w3wp程式一直在漲記憶體,也就是iis,確實是程式的鍋。

  分析記憶體問題的話,一般是在持續上漲的過程中,多抓幾個dump,看看哪些物件沒釋放,便於分析,這次使用者只抓了一個dump,要不然太大了,傳到我本機也費事。

  那就開始分析吧。

  首先找了個本地測試環境,用windbg載入dump,載入分析檔案,幸運的是,.loadby sos clr一次成功了,後續分析都沒啥問題,不用再從客戶那邊拷貝sos,clr這些檔案;

  這是使用者正常業務場景的dump,也不知道當時多少人用,都在幹什麼,既然是記憶體問題,先看下記憶體中的物件情況吧:

  !dumpheap -stat

  果不其然,出現了DataRow的影子,估計是有個大查詢沒跑了。但是是什麼場景?哪個sql?查出來多少資料?這個得繼續分析了。

  需要注意的是,抓這個dump的時候,記憶體3g多,dump大小也3g多,但是DataRow這個物件總共才46M,為什麼要看這個物件,不看其他的呢?

  要知道,分析的話,從佔用大的物件開始分析是沒問題,但是得看這個物件是不是比較特殊,像是上邊佔用最高的是string,1g多,但是string太普遍了,數量多也正常,而且不好分析,但DataRow這個肯定是訪問資料庫了,這種物件好分析。

  再就是,為什麼才46M,這個涉及到託管記憶體和非託管記憶體了。c#是基於.net的高階程式語言,所謂的託管,其實是指記憶體的託管,就是當寫程式碼需要new一個新物件的時候,是不需要考慮記憶體申請與銷燬的,.net自動給你做了,所以,當你抓了一個6g的dump,可能裡邊的託管記憶體才2g,剩餘4g非託管記憶體裡的東西從windbg是看不到的,非託管記憶體的增長,肯定是由託管程式碼引起的,所以這個地方雖然DataRow只有46M,但是由這個引起的記憶體增長是不可知的。

  那接下來就看看工作執行緒數和堆疊吧,對當時的業務場景、使用人數大概有個瞭解:

  !threads -special

  工作執行緒數不是很多,說明同時使用的人不多,應該不會有太大的壓力,所以可能是某一個或者某幾個執行緒引起的,那就看下堆疊情況,大致瞭解下使用場景,猜一下是哪個場景引起的:

  ~* e!clrstack    列印下所有執行緒的堆疊

  本來工作執行緒就不多,有業務含義並且和資料庫相關的堆疊就更少了,看了下,大致有三個場景:

  一個貌似是在判斷許可權:

  一個貌似是個財務的往來單位查詢:

  還有一個貌似是財務的憑證查詢:

  具體是哪個引起的就得繼續分析了,可以分別進入這三個執行緒,看下執行緒裡的物件情況。

  比如,切換到往來單位查詢的那個執行緒裡(執行緒id是107),然後通過!dso命令檢視下當前執行緒的物件:

  ~107 s

  !dso

  直接就看到一個sql了,看下這個執行緒裡的DataRow吧,看看它是不是它的鍋:

  !do 0000006231d5af28

  檢視下所在的DataTable資訊:

  !do 0000006027566878 

  從elementColumnCount能看出來,當前是查詢出來了89列;看的時候,要看下Type列,看看當前的物件型別,哪些是你需要和關注的。

  然後再看recordManager,看下查詢出來的記錄資訊,也就是行資訊:

  !do 0000006027566b08 

  recordCapacity,行容量,524288,即查詢出來的行為52w。

  至此,應該明瞭了,就是往來單位查詢場景,上邊的那個sql(當然也可以通過!dso中sqlCommand物件檢視Text,確認具體的sql),查詢出來了52w行,89列,導致的記憶體暴漲。

  後來通過跟開發與使用者確認,確實是這個查詢介面上,沒有選具體的單位,然後關聯了一張600w資料的表,最終查出了52w行資料導致的。實際使用者的業務場景是需要具體單位的,這種場景沒做跨單位查詢的許可權控制,使用者又恰巧沒選單位,所以出現了這個問題。後來開發把單位加了必選,該問題解決。

相關文章