在SQL Server中,咱們要看懂執行計劃和統計信息,咱們可能須要深入理解一些關鍵詞,例如密度(Density)、選擇性(Selectivity)、謂詞(predicate)、基數(Cardinality)。前陣子,對密度和選擇性的概念模糊了,恰好看了Query Tuning Fundamentals: Density, Predicates, Selectivity, and Cardinality這篇文章, 遂結合本身的理解、以及相關案例、分析總結一下這些專業名稱。html
謂詞(predicate)算法
什麼是謂詞呢?謂詞是取值爲 TRUE、FALSE 或 UNKNOWN 的表達式。 謂詞用於WHERE子句和HAVING子句的搜索條件中,還用於FROM子句的聯接條件以及須要布爾值的其餘構造中。官方的解釋爲:A predicate is an expression that evaluates to True or False 。在WHERE條裏面的常見的謂詞形式有:sql
1: LIKE模糊查詢。express
2: BETWEEN範圍查詢app
3: IS NULL、IS NOT NULL判斷ide
4: IN - ORoop
5: EXIST優化
6: 等值查詢ui
..............................lua
咱們先經過例子來看看一個謂詞(predicates)吧。以下所示, h.SalesOrderID > 43669 這個範圍查詢就是一個過濾謂詞。以下所示,在實際執行計劃中,右鍵單擊「Clustered Index Seek"查看細節。就會看到Seek Predicates。
USE AdventureWorks2014
GO
SELECT h.*
FROM Sales.SalesOrderHeader h
WHERE h.SalesOrderID > 43669;
SQL Server中有兩種謂詞:過濾謂詞和鏈接謂詞 ,還有所謂的SARG謂詞和非SARG謂詞概念。如上所示,上面的謂詞就屬於過濾謂詞,而位於LEFT/INNER/RIGHT JOIN的ON後面的爲鏈接謂詞。 另外在SQL Server中還有隱式謂詞(implied predicates)的概念。使用跟蹤標記2324能夠禁用隱式謂詞。 這裏對這些概念不作展開介紹。
密度(Density)
密度(Density)這個指標是用來衡量一個(或一組)列中,有多少惟一值。 它是一個比率值。 實際應用中值越小越好。不過,首先咱們要區分DBCC SHOW_STATISTICS輸出的頭部信息(STAT_HEADER)中的這個Density指標和DENSITY_VECTOR中的Density指標。這二者是有所區別的,其實通常咱們所說的密度(Density)指DENSITY_VECTOR中密度,而不是STAT_HEADER中的Density。
在DBCC SHOW_STATISTICS輸出的頭部信息(STAT_HEADER),這個Density指標,官方文檔的介紹以下,具體參考DBCC SHOW_STATISTICS (Transact-SQL)連接:
Density:密度計算公式爲 1/統計信息對象第一個鍵列中的全部值(不包括直方圖邊界值)的非重複值。 查詢優化器不使用此 Density 值,顯示此值的目的是爲了與 SQL Server 2008 以前的版本實現向後兼容
Calculated as 1 / distinct values for all values in the first key column of the statistics object, excluding the histogram boundary values.
This Density value is not used by the query optimizer and is displayed for backward compatibility with versions before SQL Server 2008.
可是這裏發現頭部信息(STAT_HEADER)中Density的值計算並不像官方文檔介紹的那樣(具體見上面所述,這也是我很困惑的地方,我的猜想是文檔有錯誤,一直沒人更正,畢竟官方文檔也不能保證100%的準確性):
STAT_HEADER的Density的的計算公式爲 ~= count(disitnct column_name)/count(*)
0.607627522644 ~= 0.6162394
注意:上面只能是約等於,不是等於關係。後面找了不少資料,發現其實(STAT_HEADER)中的這個Density指標的計算公式是這樣:
density =
(select distinct (column_name)
from table_name
where column_name not in (histogram range_hi_key values))
/ (select count(column_name)
from table_name
where column_name not in (histogram range_hi_key values))
具體到這個例子來講(對於複合索引,這個字段是符合索引第一個字段),以下所示:
SELECT COUNT(DISTINCT CustomerID)*1.0/COUNT(*)
FROM Sales.SalesOrderHeader
WHERE CustomerID NOT IN ( 11000, 11019, 11091, 11142, 11185, 11223, 11262,
11300, 11331, 11417, 11439, 11498, 11519, 11566,
11631, 11677, 11711, 11769, 11892, 11935, 12008,
12054, 12127, 12196, 12291, 12321, 12363, 12489,
12559, 12616, 12760, 12880, 12969, 13038, 13096,
13175, 13231, 13270, 13474, 13575, 13608, 13652,
13756, 13823, 13944, 13988, 14096, 14162, 14265,
14341, 14612, 14860, 14943, 15048, 15114, 15177,
15521, 15625, 15687, 15932, 15974, 16237, 16513,
16583, 16641, 16758, 16855, 16959, 17026, 17103,
17181, 17260, 17335, 17551, 17619, 17715, 17788,
17832, 17930, 18047, 18125, 18223, 18294, 18390,
18452, 18620, 18712, 18749, 19031, 19289, 19339,
19420, 19499, 19585, 20051, 20159, 20245, 20576,
20779, 20862, 20960, 21046, 21248, 21470, 21574,
21807, 21916, 22122, 22344, 22826, 23136, 23267,
23578, 23725, 24159, 24257, 24466, 24754, 24887,
25114, 25400, 25555, 25819, 25916, 25995, 26127,
26276, 26564, 26686, 26841, 27197, 27361, 27672,
28050, 28389, 28749, 28919, 29105, 29270, 29448,
29508, 29603, 29669, 29698, 29723, 29795, 29857,
29927, 29990, 30023, 30096, 30117, 30118 )
因爲查詢優化器不使用此Density值,因此在此略過。咱們下面來看看密度向量(DENSITY_VECTOR)中的密度計算。
密度向量(DENSITY_VECTOR)
USE AdventureWorks2014;
GO
DBCC SHOW_STATISTICS('Sales.SalesOrderHeader', 'IX_SalesOrderHeader_CustomerID') WITH DENSITY_VECTOR
密度向量中的密度(density):一個比率值,顯示在一個(組)列中有多少惟一值.(實際應用中值越小越好) 計算公式爲 1/統計信息對象第一個鍵列中的全部值(不包括直方圖邊界值)的非重複值
Density = 1 / Number of distinct values for column(s)
下表對指定 DENSITY_VECTOR 時結果集中所返回的列進行了說明。
列名 |
描述 |
All Density |
密度爲 1/非重複值。 結果顯示統計信息對象中各列的每一個前綴的密度,每一個密度顯示一行。 非重複值是每一個行前綴和列前綴的列值的非重複列表。 例如,若是統計信息對象包含鍵列 (A, B, C),結果將報告如下每一個列前綴中非重複值列表的密度:(A)、(A,B) 以及 (A, B, C)。 使用前綴 (A, B, C),如下每一個列表都是一個非重複值列表:(3, 5, 6)、(4, 4, 6)、(4, 5, 6) 和 (4, 5, 7)。 使用前綴 (A, B),相同列值具備如下非重複值列表:(3, 5)、(4, 4) 和 (4, 5) |
Average Length |
存儲列前綴的列值列表的平均長度(以字節爲單位)。 例如,若是列表 (3, 5, 6) 中的每一個值都須要 4 個字節,則長度爲 12 個字節。 |
「列」 |
爲其顯示 All density 和 Average length 的前綴中的列的名稱。 |
USE AdventureWorks2014;
GO
DBCC SHOW_STATISTICS('Sales.SalesOrderHeader', 'IX_SalesOrderHeader_CustomerID') WITH DENSITY_VECTOR
--計算字段CustomerID的Density
SELECT 1.0 / COUNT(DISTINCT CustomerID)
FROM Sales.SalesOrderHeader;
--計算字段CustomerID, SalesOrderID的Density
SELECT 1.0 / COUNT(*)
FROM ( SELECT DISTINCT
CustomerID ,
SalesOrderID
FROM Sales.SalesOrderHeader
) T;
Density = 1 / Number of distinct values for column(s)
注意,若是有多個字段,那麼就按上面方法依此類推。
其實,對於密度(density)值很大的字段,那麼能夠認爲這個字段的惟一值不多。 . Density values range from 0 to 1.0 。若是這個值小於0.1,通常講這個索引的選擇性比較高,若是大於0.1,他的選擇性就不高了。
選擇性(Selectivity)
什麼是選擇性(Selectivity)呢,選擇性也是一個比率值,它反應數據集裏重複的數據量的比例(多少),或者反過來來講,值惟一的數據量有多少比例。若是一個字段的數據不多有重複值,那麼它的選擇性就很高,高選擇性意味着高惟一性。它的取值範圍爲0~ 1。密度與選擇性成反比,密度越小,選擇性的值越大。當查詢優化器(query optimizer)讀取 SQL 時,選擇性的高低程度決定了索引是否應該用來執行該操做。經過對索引的 Statistics進行處理分析,查詢優化器能夠做出決定。基本上,它會權衡使用索引來遍歷選擇所需的記錄或者對錶進行掃描這兩種方式。
選擇性(Selectivity) = 列惟一鍵(Distinct_Keys)/行數(Num_Rows)的比值。
若是選擇率高也就是說,大量行均可以用索引鍵值來惟一標識——那麼該SQL Server評價索引就具備高選擇性,即對優化器來講也是有用的。最佳的選擇性是1,即每一行都有一個惟一的索引鍵值。低選擇性意味着表中有許多重複的鍵值,這樣的索引將不多有用。SQL Server優化器基於索引的選擇性來決定對一個查詢是否使用索引。越高的選擇性,SQL Server檢索結果集(Result set)就越快和越有效
選擇性最經常使用於描述謂詞,官方文檔「Query Processing Architecture Guide」關於選擇性的一段介紹以下:
SQL Server查詢優化器在估計用於從表或索引中提取信息的不一樣方法所需的資源成本時,依賴於統計信息的分佈。 爲列和索引相關字段保留分佈有關的統計信息,並保存有關基礎數據的密度信息。 這些信息代表特定索引或列中的值的選擇性。 例如,在一個表明汽車的表中,不少汽車出自同一製造商,但每輛車都有惟一的車牌號 (VIN)。 VIN 的密度比製造商低,因此 VIN 索引比製造商索引更具選擇性。 若是索引統計信息不是當前的,則查詢優化器可能沒法對錶的當前狀態作出最佳選擇。 有關密度的詳細信息,請參閱統計信息。
密度定義數據中存在的惟一值的分佈,或給定列的重複值平均數。 密度與選擇性成反比,密度越小,值的選擇性越大。
基數(Cardinality)
基數(Cardinaltiy)簡單一點來講,能夠被認爲是查詢運算符(Index Seek、Nested Loop Join,Filter....)返回的行數。查詢計劃中的每一個運算符都具備估計的基數(優化器猜想運算符將返回的行數)和實際基數(運算符實際返回的行數)。您能夠經過運行「SET STATISTICS PROFILE ON」或查看實際執行計劃查詢來查看。以下截圖所示: Actual Number of Rows 與 Esimated Number of Rows
優化器有不少方式估算基數的算法,咱們這裏列舉幾種簡單的方式,若是你想了解更多基數估計的算法。能夠參考」SQL Server中關於基數估計如何計算預估行數的一些探討「或官方文檔Optimizing Your Query Plans with the SQL Server 2014 Cardinality Estimator
若是謂詞很簡單,如「CustomerID = 11142」,而且搜索值剛好是直方圖RANGE_HI_KEY(直方圖梯級的上限列值端),則EQ_ROWS可用於很是準確的估計基數。以下所示:
USE AdventureWorks2014
GO
SELECT *
FROM Sales.SalesOrderHeader
WHERE CustomerID =11142;
若是查詢條件的值剛好落在兩個步驟RANGE_HI_KEY的端點之間,那麼該特定直方圖步驟中的EQ_ROWS用於估計謂詞選擇性和操做者基數。
USE AdventureWorks2014
GO
SELECT *
FROM Sales.SalesOrderHeader
WHERE CustomerID =11222;
以下所示,Esimated Number of Rows的取值就來源於AVG_RANGE_ROWS . 由於11222位於11185 與 11223之間。因此取RANGE_HI_KEY=11223這條記錄對應的AVG_RANGE_ROWS(4.32432)。
3: 若是在查詢條件中使用變量(編譯時未知特定搜索值),則預估行數(Esimated Number of Rows)= 密度* 採樣的行數:
[Row Sampled ]* [ALL density ]
USE AdventureWorks2014
GO
DECLARE @CustomerID INT;
SET @CustomerID=11222
SELECT *
FROM Sales.SalesOrderHeader
WHERE CustomerID =@CustomerID;
有時,查詢優化器沒法準確預測相關運算符返回的行數, 這個會妨礙查詢優化器準確的估計查詢計劃的成本,從而致使選擇一個較差的執行計劃。基數估計錯誤是SQL Server中查詢計劃速度緩慢的最多見緣由之一,所以在調優過程當中,瞭解如何在查詢計劃中識別基數估計問題很是重要。
參考資料:
https://docs.microsoft.com/zh-cn/sql/relational-databases/statistics/statistics?view=sql-server-2017#density