Buffer cache hit ratio效能計數器真的可以作為SQL Server 記憶體瓶頸的判斷指標嗎?
SQL Server中對於Buffer cache hit ratio的理解:
Buffer cache hit ratio官方是這麼解釋的:“指示在緩衝區快取記憶體中找到而不需要從磁碟中讀取的頁的百分比。”
Buffer cache hit ratio被很多人當做判斷記憶體的效能指標之一(當然沒說僅僅只看這個計數器的值,實際上現在都不怎麼看這個值了),
也有不少給給出了具體的引數,諸如(OLTP)要大於95%,或者是大於98%之類的,我不知道給出具體參考值的人是不是真是地區測試過這個引數的值,是作為經驗總結還是複製貼上?
當我去伺服器上觀察這個值的時候,似乎發現一個規律,
不管伺服器的負載如何,即便是存在較重的業務負載的時候,這個值一直是接近所謂的“理想值”(99%),難道這個值真的可以去作為衡量記憶體瓶頸的指標嗎,
實際上被這個問題困惑了好多天,
我在測試的時候,儘管不斷地去壓縮SqlServer的最大記憶體限制,
然後做壓力測試,
儘管Page life expectancy可以底到十幾二十幾毫秒,也就是記憶體已經存在很嚴重的瓶頸了,卻發現Buffer cache hit ratio這個計數器的值是99%左右,
於是開始懷疑這個計數器的演算法,如果說緩衝命中率達到99%左右,能否說明沒有記憶體瓶頸呢?
其實如果做過實際測試,應該不難發現這個問題,對於這個值,早就有人懷疑過了,明明是存在記憶體的瓶頸,快取命中率卻顯示為99%
只是沒發現有人提供滿意的答案,具體問題可以參考這個
下面演示一下測試步驟,測試過程可能比較粗粗略,說明其中原理即可
1,首先限制SqlServer的最大記憶體為1G,然後依次讀取容量大於1G空間的不同的表,看看效能計數器給出的結果
,
2,建立一張測試表,往裡面寫入將近1G的資料量
然後再建立跟這個表一樣大小的表,目前,這兩個表的資料都接近於1G的空間
select * into DBTEST2.dbo.TestBufferCacheHitRatio_BAK from TestBufferCacheHitRatio
3,我們知道SqlServer讀取資料的時候,粗略地講,(如果資料不在快取中)是現將資料讀取到記憶體,然後再將資料返回給客戶端,
測試是我在本機完成的,本機資料庫伺服器沒有任何負載,測試的兩個庫也是新建的空資料庫
造完測試資料之後,
測試之前我先清除所有快取,dbcc dropcleanbuffers,
然而,由於限制了SqlServer的最大記憶體限制而1G,忽略SqlServer非資料快取佔用的記憶體空間,可以粗略地認為,
當對第一個表讀取完後,這個表基本上佔據滿了SqlServer可用記憶體空間,
如果繼續讀取另外一張類似表的資料的時候,就要從磁碟上讀取了
(實際上已經清除快取了,主要是為了說明,第一次查詢佔滿了記憶體,第二次查詢必然要從磁碟讀取到記憶體,記憶體中沒有第二次查詢的資料的快取,即便是不清除快取,也是一個效果),
此時觀察Buffer cache hit ratio計數器的值,
理論上說,此時第二張表的資料是直接從磁碟上讀取的,也就不存在所謂的快取,快取命中率應該是一個非常低的值,甚至是0,
如果實際來觀察所謂的“快取命中率”的值,看看是什麼結果
截圖是第一個查詢執行完成之後,執行第二個查詢的時候,Buffer cache hit ratio效能計數器的情況,
第二個查詢執行完成之後,我暫停了計數器監控,
這個結果應該是不受外界因素影響的(再次說明,我本機資料庫沒有任何負載,純粹本機做測試的一個例項,也不用反覆測試,我反覆測試了N次了,下面會說明原因所在)
從截圖可以看到,在第一個查詢執行完成之後,
第二個查詢執行的過程中,快取命中率竟然沒有明顯的下降,最小值也是96%,平均值高達99%,第二個表的資料命名是從磁碟讀取的,當然通過IO也可以觀察出來,純粹的預讀
這不扯淡嗎,測試之前清空過快取,並且,現有記憶體已經被第一個查詢佔據滿了,
明明第二張表的資料純粹第是從硬碟空間讀取的,為什麼快取命中率Buffer cache hit ratio竟然高達99%,
難怪之前我觀察任何一臺伺服器的快取命中率(Buffer cache hit ratio),即便是業務高峰期,都是在99%以上,原因在哪裡?
何為Buffer cache hit ratio?
原來是Buffer cache hit ratio這個計數器在計算快取命中率的時候,
把read ahead read,也即預讀讀取出來的資料,也算是“快取”了,只有物理讀也即physical read算作非快取,難怪Buffer cache hit ratio總是有這個高的值
那麼就來說說預讀,實際上預讀是什麼?
預讀是指在在查詢執行之前,預估查詢可能要用到的資料,在查詢執行之前將資料讀取到記憶體中,
所以,也不難理解,為什麼沒有把預讀產生的資料作為快取資料來處理。
真正在查詢的時候,發現數據不在快取中,再次去磁碟上讀取資料,此時為物理讀,而真正沒有在“快取”中命中的資料,就是這部分物理讀,
所以快取命中率中所謂“命中”的快取的部分,是包含了已快取的資料和預讀的資料。
但是預讀所讀取出來的資料,雖然是從磁碟上讀取出來的,但是在計算快取的時候,是把這部分資料當做了快取的
那麼怎麼證明呢?
可以通過652這個TRACE禁用預讀(read ahead read),再同樣的測試,看看現在的快取命中率
執行DBCC TRACEON(652, -1)之後的測試截圖
可以看到,本次同樣的測試,第一個查詢完成之後,第二查詢開始,快取命中率有一個斷崖式的下跌,大多數時間是0 ,
平均值也不過是3%的樣子(至於為什麼存在瞬時快取命中率的非0的高點,個人猜測是SqlServer快取的一些程序讀取到的元資料快取)
如果觀察IO的話,發現現在的第二個查詢沒有了預讀(read ahead read),全部是物理讀(physical read)
這也說明,對於Buffer cache hit ratio這個效能計數器的演算法,是把預讀讀取出來的資料也算作是“快取”了,如果拿這個值去判斷記憶體瓶頸,是沒有參考意義的,當然對於記憶體瓶頸的判斷,可以用其他計數器
問題自己理解起來容易,但是是一邊測試一邊截圖,要做到恰到好處,把問題說明清楚,表達出來真不容易。以後多寫些東西鍛鍊,
總結:
在進行記憶體瓶頸判斷的時候,
Buffer cache hit ratio這計數器的值,是不具備參考意義的,即便是觀察到Buffer cache hit ratio命中率很高,也不一定代表伺服器上沒有記憶體瓶頸,
如果Buffer cache hit ratio命中率很低,極有可能說明存在記憶體瓶頸,此時還要藉助於其他計數器來判斷是否存在記憶體瓶頸,單純一個Buffer cache hit ratio無法判斷記憶體瓶頸。
後記,對於自己寫的東西,經常是誠惶誠恐,生怕誤導了別人,同時發現網上有非常多的文章,提到Buffer cache hit ratio,說的似乎是言之鑿鑿,具體的參考值都給到了,不知道到底有沒有去手動驗證一下?
最後補上用SQL查詢查詢快取命中率的指令碼
SELECT CAST(CAST((a.cntr_value * 1.0 / b.cntr_value)*100 as int) AS VARCHAR(20)) as BufferCacheHitRatio FROM ( SELECT * FROM sys.dm_os_performance_counters WHERE counter_name = 'Buffer cache hit ratio' AND object_name = CASE WHEN @@SERVICENAME = 'MSSQLSERVER' THEN 'SQLServer:Buffer Manager' ELSE 'MSSQL$' + rtrim(@@SERVICENAME) + ':Buffer Manager' END ) a CROSS JOIN ( SELECT * from sys.dm_os_performance_counters WHERE counter_name = 'Buffer cache hit ratio base' and object_name = CASE WHEN @@SERVICENAME = 'MSSQLSERVER' THEN 'SQLServer:Buffer Manager' ELSE 'MSSQL$' + rtrim(@@SERVICENAME) + ':Buffer Manager' END ) b