有個問題:在執行計劃裏運算符的估計行數是42,可是你知道查詢的正確行數不是42。你也據說了SQL Server使用統計信息來做此估計的?但咱們怎麼看懂統計信息,來理解這裏的估計是怎麼來的?html
今天我想談下SQL Server裏的統計信息,在直方圖(histogram)和密度向量(density vector)裏,SQL Server內部是如何保存這些值的並用此來估計行數的。web
首先咱們來看下直方圖。直方圖的用途是用高效、壓縮的方式存儲列數據分佈狀況。每次當你在表上建立索引時(彙集/非彙集索引),SQL Server會爲你自動建立統計信息。這個統計信息就包含了那列(索引鍵)的數據分佈信息。好比你有一個訂單表,裏面有個Country列,這列裏有不少國家名字。所以直方圖就是對這些國家個數分佈狀況的可視化:sql
在直方圖裏,咱們用不少柱條描述數據分佈狀況:柱條越高,那列的這個值就記錄數就越多。SQL Server使用一樣的概念和格式來描述數據分佈狀況。咱們經過一個例子來詳細瞭解下。在AdventureWorks2008R2數據庫裏,咱們找到表SalesOrderDetail裏的ProductID列。這ProductID列存儲着具體的銷售產品ID信息。能夠看到,ProductID列也有索引定義,那就說有對應的統計信息來描述ProductID列的數據分佈狀況。數據庫
在SSMS裏,你經過查看錶屬性來查看列和統計信息,也可使用DBCC SHOW_STATISTICS命令在結果裏輸出統計信息。 ide
1 -- Show the statistics for a given index 2 DBCC SHOW_STATISTICS ('Sales.SalesOrderDetail', IX_SalesOrderDetail_ProductID) 3 GO
從上圖能夠看到,這個命令返回3個不一樣的記錄集:工具
咱們來關注下這3個部分信息,看看它們是如何被用來作參數預估(Cardinality Estimation) (估計行數的計算)。如今咱們對SalesOrderDetail表執行一個簡單的查詢,點擊工具欄的顯示包含實際的執行計劃。如你所見,咱們只要ProductID列值爲707的記錄:學習
1 -- SQL Server使用EQ_ROWS值來作預估,這個值在直方圖裏能夠直接取到。 2 -- 對於篩選器運算符估計行數是3083. 3 SELECT * FROM Sales.SalesOrderDetail 4 WHERE ProductID = 707 5 GO
查詢返回121317條記錄中的3083條記錄。由於咱們沒有定義覆蓋非彙集索引(這裏也用不到,由於用了SELECT *),這個查詢已經越過臨界點了,從執行計劃裏能夠看到,SQL Server已經選擇了非彙集索引掃描運算符。spa
在執行計劃裏,篩選器運算符的屬性信息(鼠標移到運算符上會顯示屬性信息)的謂詞部分,這裏顯示了過濾記錄條件是ProductID值是707,還有估計行數是3083。看來這裏的統計信息很是準確。但問題是這個估計是從哪裏來的呢?當你看直方圖時,咱們能夠看到不少行(最大梯級(步長)數爲 200),這裏描述ProductID列數據分佈狀況。3d
直方圖的每一行有如下列:指針
從RANGE_HI_KEY列能夠看到,ProductID值爲707的記錄有3083。這與咱們查詢的限制條件徹底匹配。在這個狀況下,SQL Server使用EQ_ROWS列的值用做參數預估——這裏是3083。這就是執行計劃裏篩選器運算符用到的估計方法。
咱們再來看個查詢:
1 -- 值爲915記錄數在直方圖裏不能直接取到,所以SQL Server使用AVG_RANGE_ROWS列值來作預估。 2 -- 在910到916之間有150條記錄,不一樣值個數是4(DISTINCT_RANGE_ROWS)。 3 -- 所以對於非彙集查找,SQL Server估計150/4=37.5條記錄。 4 SELECT * FROM Sales.SalesOrderDetail 5 WHERE ProductID = 915 6 GO
這裏咱們只返回ProductID列值爲915的記錄。可是在直方圖裏,咱們找不到915的對應值。直方圖裏存儲了910到916之間的值。這個範圍內的記錄數有150條(RANGE_ROWS),不包括910和916這2個值。在這個150條記錄裏,有4個不一樣值(DISTINCT_RANGE_ROWS)。這就是說915的記錄數在910與916之間是37.5(AVG_RANGE_ROWS=150/4)。
所以在這個狀況下,SQL Server對915值的估計行數是37.5,如你在執行計劃所見。事實上,非彙集索引查找運算符返回41條記錄,這個估計仍是很準的。
從這個例子裏能夠看出,在直方圖裏沒有徹底匹配值時,SQL Server也能進行基數計算。所以在直方圖裏會有RANGE_ROWS列和DISTINCT_RANGE_ROWS列。從上述解釋能夠看出,直方圖並不難理解。直方圖裏很重要的一點是,SQL Server只爲索引中第1個鍵列中的列值建立直方圖。索引中的全部後續列,SQL Server在密度向量裏存儲。所以,在組合索引鍵裏,第1列應該是選擇性最高的那列(查詢常常用到的)。
咱們再來看看神祕的密度向量,看下非彙集索引IX_SalesOrderDetail_ProductID,這個索引只在ProductID列創建。可是每一個非彙集索引,SQL Server在索引的頁層也保存彙集鍵做爲邏輯指針。當你定義了非惟一的非彙集索引,彙集鍵也是非彙集索引導航結構的一部分。表裏的彙集鍵SalesOrderID是個組合列,包含SalesOrderID列和SalesOrderDetailID列。
這就是說咱們的非惟一非彙集索引事實上包含ProductID,SalesOrderID和SalesOrderDetailID列。索引鍵是個組合鍵。一樣SQL Server須要爲其餘列建立密度向量,由於只有第1列(ProductID)是直方圖裏有信息,這個在上一部分咱們已經看過了。當你看用DBCC SHOW_STATISTICS命令的輸出時,密度向量是第2個表信息。
SQL Server在這裏存儲選擇率(selectivity),不一樣列組合的密度。例如,ProductID列的All density值是0.003759399,你能夠用下列語句來驗證下:
1 -- The "All Density" value for the column ProductID: 0,0037593984962406015 2 SELECT 1 / CAST(COUNT(DISTINCT ProductID) AS NUMERIC(18, 2)) FROM Sales.SalesOrderDetail 3 GO
對於ProductID,SalesOrderID組合列和ProductID,SalesOrderID,SalesOrderDetailID組合列的All density值分別是8.242868E-06和8.242868E-06。你能夠用1除以2個組合列的惟一值來驗證下。這裏咱們的記錄是121317,這些彙集值(SalesOrderID,SalesOrderDetailID組成了彙集鍵)都是惟一的,咱們能夠計算下:1/121317=8.242867858585359e-6。如今的問題是,SQL Server如何使用這些密度向量值做參數預估呢?
咱們來看一個查詢:
1 -- SQL Server uses the reciprocal in a GROUP BY to make an estimation how 2 -- much rows are returned: 3 -- Estimation for the Stream Aggregate: 266 4 SELECT ProductID FROM Sales.SalesOrderDetail 5 GROUP BY ProductID 6 GO
咱們在ProductID列進行GROUP BY操做。在這個狀況下,SQL Server使用ProductID列的密度向量值來估計流聚合運算符的估計行數:1/0.003759399=266。在執行計劃裏流聚合運算符的屬性信息裏能夠看到估計行數是266。
在T-SQL語句裏,當你使用本地變量時,SQL Server不能嗅探任何參數值,只能退回使用密度向量來進行參數預估。咱們看下面的查詢。
1 -- SQL Server also uses the Density Vector when we are working with local variables 2 -- and equality predicates. 3 -- SQL Server estimates for the Non-Clustered Index Seek 456 records: 121317 * 0,003759 = 456 4 -- Every variable value gives us the same estimation. 5 6 -- Estimated: 456 7 -- Actual: 3083 8 DECLARE @i INT = 707 9 10 SELECT * FROM Sales.SalesOrderDetail 11 WHERE ProductID = @i
SQL Server對篩選器運算符的估計行數是456(121317 * 0.003759399),但實際上咱們只返回了44條記錄。
當你的本地變量與大於小於組合時,SQL Server再也不使用密度向量值,只假設30%的行返回。
1 -- When we are using an inequality predicate (">", "<") SQL Server assumes 30% for the 2 -- estimated number of rows. 3 -- Estimated: 36.395 (121.317/36.395 = 3,33) 4 -- Actual: 44 5 DECLARE @i INT = 719 6 7 SELECT * FROM Sales.SalesOrderDetail 8 WHERE ProductID > @i 9 GO
從執行計劃裏能夠看到,SQL Server對此的估計行數是36395,由於這就是全表30%的記錄數(12317 * 0.30)。
在這篇文章裏你學到了SQL Server如何使用內在的統計信息,對咱們的查詢執行參數預估。統計信息包含2個部分:直方圖,還有密度向量。在直方圖裏,SQL Server能夠很是容易的估計出查詢的平均返回行數。由於SQL Server只存儲組合索引鍵第1列的直方圖信息,另外對於其餘列的信息在密度向量裏存儲。還有咱們學習了這2個統計信息在參數預估時如何使用的。
https://www.sqlpassion.at/archive/2014/01/28/inside-the-statistics-histogram-density-vector/