使用者報案,某網頁效能變得奇慢無比,簡單的上線公告查詢耗時超過兩分鐘,追查後抓出問題查詢如下例:

select
case when 
convert(varchar,u.StartDate,108)='00:00:00' and 
convert(varchar,u.EndDate,108)='00:00:00' 
then convert(datetime,convert(varchar,u.EndDate,111))+1 
else EndDate end as EndDate
,u.SomeNTextCol
,u.MoreCols
,u.Priority
,dbo.ufn_GetDisplayText('Priority',u.Priority) as PriorityName,
c.CateId
from Post u join Category c on u.UID=c.uid

查詢結果共4939筆,總資料約40MB,耗時卻達80秒以上,查詢緩慢現象用SSMS手動查詢亦可重現。原本懷疑卡在User Defined Function,排除後速度依舊。最後對照測找出NTEXT欄位是關鍵,資料表Post有多個NTEXT欄位,只要SELECT包含任何NTEXT欄位,速度就會慢到一分鐘以上;若完全不加NTEXT欄位,3秒內解決。

依我的理解,NTEXT欄位效率不如NVARCHAR(MAX),但差異誇張若此並不合理。檢視伺服器狀況,發現疑點:

全部4GB記憶體耗用超過95%,堂堂SQL Server竟只有不到300MB記憶體可用,且CPU%偏高,而Reporting Service吃掉了近600MB記憶體。莫非效能問題是記憶體不足引起?

使用Set Statistics IO ON取得IO統計數據,推測獲得證實:

(4939 row(s) affected)
Table 'Category'. Scan count 1, logical reads 21, physical reads 17, read-ahead reads 17, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Post'. Scan count 1, logical reads 325, physical reads 320, read-ahead reads 318, lob logical reads 82618, lob physical reads 10819, lob read-ahead reads 1333.

查詢NTEXT欄位涉及LOB讀取,於本例產生82618個讀取數,其中10819依賴實體磁碟讀取,反覆執行也不會下降,代表SQL Server無法藉由Cache提升效能,足以推論記憶體不足是效能瓶頸。

重啟Reporting Service釋放記憶體,SQL Server記憶體量上升到460MB,查詢速度瞬間加速到3秒以內。

(4939 row(s) affected)
Table 'Category'. Scan count 1, logical reads 23, physical reads 1, read-ahead reads 22, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Post'. Scan count 1, logical reads 325, physical reads 0, read-ahead reads 0, lob logical reads 82618, lob physical reads 0, lob read-ahead reads 1333.

由IO統計,實體讀取數由10819降至0,意味著所有資料均由Cache取得,是速度提升主因。

所以這台4GB的SQL主機記憶體不足要加RAM?不然,SQL Server只用了不到300MB,嚴格來說,不是記憶體不足,而是SQL搶不到記憶體。由Task Manager的清單,所有Process耗用的記憶體數量加總不到2G,還有近2G到哪裡去了?想知道Windows記憶體用到哪裡去,RAMMap是絕佳工具:

答案揭曉,Process Private總計約1.8GB,與Task Manager各Process用量加總相近。而Mapped File與Metafile為Windows檔案Cache,佔用近1GB,理論上應讓出空間給SQL使用以改善效能。該SQL總資料量不足500MB,屬於小型資料庫(這也是機器只配4G記憶體的理由),最後決定將SQL起始最小記憶體數設為512MB起跳,避免未來再發生SQL搶輸記憶體的場面。

留下一則疑惑:我們都知道Windows會積極透過檔案Cache提升效能,但在記憶體不足時,Windows難道不會主動釋放Cache舒緩記憶體吃緊狀況?

會這樣想是因為ASP.NET就有這種聰明設計:參考

Cache 類別會提供強大的功能,讓您自訂快取項目的方式和快取的時間長短。例如,當系統記憶體不足時,快取會自動移除不常使用或低優先權的項目,以釋放記憶體。這個技術稱為清除 (Scavenging),也是快取可確定過時資料不會耗用寶貴的伺服器資源的其中一種方法。

這是我心中理想的記憶體管理策略,但依本案例與先前經驗,Windows的檔案Cache策略並不如.NET Cache有彈性。關於Windows會不會釋出檔案Cache解決記憶體不足危機,我一直沒找到明確官方文件證實,倒是爬文找到一篇相關文章

If you’re on Windows Server 2008 or newer, you should also make sure that the Windows file system cache isn’t eating up all of your memory. Windows will cache data for file system access when you drag and drop a file, copy it with xcopy, or push a backup across the network. This behavior is by design because it’s supposed to add to the feeling of faster performance for the end user. Unfortunately, it also steals RAM from server-side processes like SQL Server.
Windows 2008起會積極使用檔案Cache改善使用者的操作經驗,但顯然這會排擠SQL之類伺服器所能使用的記憶體資源。

然而,這次遇上的應屬少見案例。一般資料量大且忙碌的SQL Server,啟動後很快就會建立大量Cache,不致空留記憶體給Windows檔案Cache蠶食。這台SQL資料量很少,但會以緩慢速度增加,時間一久才會發生記憶體搶輸Windows檔案Cache的結果,面對這種狀況,保留基本記憶體量可視為有效解決方案,結案。


Comments

Be the first to post a comment

Post a comment