有個問題:在執行計劃里運算符的估計行數是42,但是你知道查詢的正確行數不是42。你也聽說了SQL Server使用統計信息來作此估計的?但我們怎么看懂統計信息,來理解這里的估計是怎么來的?
今天我想談下SQL Server里的統計信息,在直方圖(histogram)和密度向量(density vector)里,SQL Server內部是如何保存這些值的并用此來估計行數的。
直方圖(Histogram)首先我們來看下直方圖。直方圖的用途是用高效、壓縮的方式存儲列數據分布情況。每次當你在表上創建索引時(聚集/非聚集索引),SQL Server會為你自動創建統計信息。這個統計信息就包含了那列(索引鍵)的數據分布信息。比如你有一個訂單表,里面有個Country列,這列里有很多國家名字。因此直方圖就是對這些國家個數分布情況的可視化:
在直方圖里,我們用很多柱條描述數據分布情況:柱條越高,那列的這個值就記錄數就越多。SQL Server使用同樣的概念和格式來描述數據分布情況。我們通過一個例子來詳細了解下。在AdventureWorks2008R2數據庫里,我們找到表SalesOrderDetail里的PRoductID列。這ProductID列存儲著具體的銷售產品ID信息。可以看到,ProductID列也有索引定義,那就說有對應的統計信息來描述ProductID列的數據分布情況。
在SSMS里,你通過查看表屬性來查看列和統計信息,也可以使用DBCC SHOW_STATISTICS命令在結果里輸出統計信息。
1 -- Show the statistics for a given index2 DBCC SHOW_STATISTICS ('Sales.SalesOrderDetail', IX_SalesOrderDetail_ProductID)3 GO
從上圖可以看到,這個命令返回3個不同的記錄集:
我們來關注下這3個部分信息,看看它們是如何被用來做參數預估(Cardinality Estimation) (估計行數的計算)?,F在我們對SalesOrderDetail表執行一個簡單的查詢,點擊工具欄的顯示包含實際的執行計劃。如你所見,我們只要ProductID列值為707的記錄:
1 -- SQL Server使用EQ_ROWS值來做預估,這個值在直方圖里可以直接取到。2 -- 對于篩選器運算符估計行數是3083.3 SELECT * FROM Sales.SalesOrderDetail4 WHERE ProductID = 7075 GO
查詢返回121317條記錄中的3083條記錄。因為我們沒有定義覆蓋非聚集索引(這里也用不到,因為用了SELECT *),這個查詢已經越過臨界點了,從執行計劃里可以看到,SQL Server已經選擇了非聚集索引掃描運算符。
在執行計劃里,篩選器運算符的屬性信息(鼠標移到運算符上會顯示屬性信息)的謂詞部分,這里顯示了過濾記錄條件是ProductID值是707,還有估計行數是3083??磥磉@里的統計信息非常準確。但問題是這個估計是從哪里來的呢?當你看直方圖時,我們可以看到很多行(最大梯級(步長)數為 200),這里描述ProductID列數據分布情況。
直方圖的每一行有以下列:
從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.SalesOrderDetail5 WHERE ProductID = 9156 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列應該是選擇性最高的那列(查詢經常用到的)。
密度向量(density vector)我們再來看看神秘的密度向量,看下非聚集索引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,00375939849624060152 SELECT 1 / CAST(COUNT(DISTINCT ProductID) AS NUMERIC(18, 2)) FROM Sales.SalesOrderDetail3 GO
對于ProductID,SalesOrderID組合列和ProductID,SalesOrderID,SalesOrderDetailID組合列的All density值分別是8.242868E-06和8.242868E-06。你可以用1除以2個組合列的唯一值來驗證下。這里我們的記錄是121317,這些聚集值(SalesOrderID,SalesOrderDetailID組成了聚集鍵)都是唯一的,我們可以計算下:1/121317=8.242867858585359e-6?,F在的問題是,SQL Server如何使用這些密度向量值作參數預估呢?
我們來看一個查詢:
1 -- SQL Server uses the reciprocal in a GROUP BY to make an estimation how2 -- much rows are returned:3 -- Estimation for the Stream Aggregate: 2664 SELECT ProductID FROM Sales.SalesOrderDetail5 GROUP BY ProductID6 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.SalesOrderDetail11 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 the2 -- estimated number of rows.3 -- Estimated: 36.395 (121.317/36.395 = 3,33)4 -- Actual: 445 DECLARE @i INT = 7196 7 SELECT * FROM Sales.SalesOrderDetail8 WHERE ProductID > @i9 GO
從執行計劃里可以看到,SQL Server對此的估計行數是36395,因為這就是全表30%的記錄數(12317 * 0.30)。
小結在這篇文章里你學到了SQL Server如何使用內在的統計信息,對我們的查詢執行參數預估。統計信息包含2個部分:直方圖,還有密度向量。在直方圖里,SQL Server可以非常容易的估計出查詢的平均返回行數。因為SQL Server只存儲組合索引鍵第1列的直方圖信息,另外對于其他列的信息在密度向量里存儲。還有我們學習了這2個統計信息在參數預估時如何使用的。
新聞熱點
疑難解答