《Pro SQL Server Internals, 2nd edition》的CHAPTER 3 Statistics中的Introduction to SQL Server Statistics、Statistics and Execution Plans、Statistics Maintena
《Pro SQL Server Internals》
作者: Dmitri Korotkevitch
出版社: Apress
出版年: 2016-12-29
頁數: 804
定價: USD 59.99
裝幀: Paperback
ISBN: 9781484219638
統計
SQL Server查詢優化器在為查詢選擇執行計劃時使用基於成本的模型。它估計不同執行計劃的成本,並選擇成本最低的一個。但是,請記住,SQL Server並不搜尋查詢可用的最佳執行計劃,因為評估所有可能的替代方案在CPU方面都是耗時和昂貴的。查詢優化器的目標是找到足夠好的執行計劃,足夠快。
基數估計(在查詢執行的每個步驟中需要處理的行數的估計)是查詢優化中最重要的因素之一。這個數字會影響連線策略的選擇、查詢執行所需的記憶體量(記憶體授予)以及其他許多事情。
訪問資料時要使用的索引的選擇就是這些因素之一。正如您將記住的,鍵和RID查詢操作在I/O方面是昂貴的,並且SQL Server在估計將需要大量這些操作時不使用非聚集索引。SQL Server維護有關索引的統計資訊,在某些情況下還維護有關列的統計資訊,這有助於執行這樣的估計。
SQL Server統計介紹
SQL Server統計資訊在索引鍵值中,有時在常規列值中,包含有關資料分佈的資訊的系統物件。可以在支援比較操作的任何資料型別上建立統計資訊,例如>、<、=等。
讓我們檢查上一章中在清單2-15中建立的dbo.Books表中的IDX_BOOKS_ISBN索引統計資訊。可以通過使用DBCC SHOW_STATISTICS('dbo.Books' , IDX_BOOKS_ISBN)命令執行此操作。結果如圖3-1所示。
圖3-1。DBCC SHOW_STATISTICS輸出
如您所見,DBCC SHOW_STATISTICS命令返回三個結果集。第一個包含有關統計資訊的一般元資料資訊,如名稱、更新日期、更新統計資訊時索引中的行數等。第一結果集中的Steps列指示直方圖中的步驟/值的數量(稍後詳細介紹)。密度值不由查詢優化器使用,僅出於向後相容的目的而顯示。
第二個結果集,稱為密度向量,包含有關密度的資訊,用於組合來自統計(索引)的關鍵值。它是根據1/多個不同值公式計算的,它指示平均每個鍵值組合有多少行。即使IDX_Books_ISBN索引只定義了一個鍵列ISBN,它也包括作為索引行的一部分的聚類索引鍵。我們的表有12500個唯一的ISBN值,ISBN列的密度為1.0/1252500=7.984032E-07。(ISBN,BookId)列的所有組合也是唯一的,並且具有相同的密度。
最後一個結果集稱為直方圖。直方圖中的每條記錄(稱為直方圖步驟)包括統計資訊(索引)最左側列中的示例鍵值以及關於從前一個值到當前RANGE_HI_KEY值範圍內的資料分佈的資訊。讓我們更深入地研究直方圖列。
RANGE_HI_KEY列儲存金鑰的樣本值。此值是直方圖步驟定義的範圍的上限鍵值。例如,在圖3-1的直方圖中記錄(步驟)#3withRANGE_HI_KEY='104-0100002488'儲存有關從ISBN>'101-0100001796'到ISBN<='104-0100002488'的間隔的資訊。
RANGE_ROWS列估計間隔內的行數。在我們的例子中,記錄(步驟)#3定義的間隔有8191行。
EQ_ROWS指示有多少行的鍵值等於RANGE_HI_KEY上限值。在我們的例子中,只有一行ISBN='104-0100002488'。
DISTINCT_RANGE_ROWS指示在該間隔內鍵的不同值有多少。在我們的例子中,鍵的所有值都是唯一的,所以DISTINCT_RANGE_ROWS=RANGE_ROWS。
AVG_RANGE_ROWS表示間隔中每個不同鍵值的平均行數。在我們的例子中,鍵的所有值都是唯一的,因此AVG_RANGE_ROWS=1。
讓我們用清單3-1所示的程式碼將一組重複的ISBN值插入索引中。列表3-1.將重複的ISBN值插入索引。
;with Prefix(Prefix) as ( select Num from (values(104),(104),(104),(104),(104)) Num(Num) ) ,Postfix(Postfix) as ( select 100000001 union all select Postfix + 1 from Postfix where Postfix < 100002500 ) insert into dbo.Books(ISBN, Title) select convert(char(3), Prefix) + '-0' + convert(char(9),Postfix) ,'Title for ISBN' + convert(char(3), Prefix) + '-0' + convert(char(9),Postfix) from Prefix cross join Postfix option (maxrecursion 0); --更新統計資料 update statistics dbo.Books IDX_ Books_ISBN with fullscan ;
現在,如果再次執行DBCC SHOW_STATISTICS(“dbo.Books”,IDX_BOOKS_ISBN)命令,您將看到圖3-2所示的結果。
圖3-2。DBCCSHOW_STATISTICS輸出
帶有字首104的ISBN值現在有重複,這影響直方圖。值得一提的是,第二結果集中的密度資訊也發生了變化。具有重複值的ISBN的密度高於(ISBN,BookId)列的組合,這仍然是唯一的。
讓我們執行SELECT BookId,Title FROM dbo.Books WHERE ISBN LIKE'114%'語句並檢查執行計劃,如圖3-3所示。
圖3-3。查詢的執行計劃
大多數執行計劃操作符有兩個重要的屬性。實際行數指示在操作符執行期間處理了多少行。估計行數指示SQL Server在查詢優化階段為該操作符估計的行數。在我們的例子中,SQL Server估計有2625行,ISBN從114開始。如果檢視圖3-2所示的直方圖,您將看到,步驟10儲存了關於ISBN間隔的資料分佈的資訊,其中包括您正在選擇的值。即使使用線性近似,也可以估計接近SQL Server所確定的行數。
關於統計學,有兩件事情需要牢記。
1. 直方圖僅儲存最左側統計(索引)列的資料分佈資訊。統計學中有關於鍵值的多列密度的資訊,但就是這樣。直方圖中的所有其他資訊僅涉及最左邊的統計列的資料分佈。
2. SQL Server最多保留直方圖中的200個步驟,而不管表的大小以及表是否被分割槽。每個直方圖步驟所覆蓋的間隔隨著表的增長而增加。這導致對於大型表的統計資訊不那麼準確。
在複合索引的情況下,當索引中的所有列都用作所有查詢中的謂詞時,將具有較低密度/較高唯一值百分比的列定義為索引的最左側列是有益的。這將允許SQL Server更好地利用統計資料中的資料分佈資訊。但是,您應該考慮謂詞的SARGa.。例如,如果所有查詢都在where子句中使用[email protected]和[email protected]謂詞,那麼最好將LastName作為索引中最左邊的列。然而,對於像[email protected]和LastName<>@LastName這樣的謂詞,情況並非如此,其中LastName不可SARGable。
統計和執行計劃
預設情況下,SQL Server自動建立和更新統計資料。在資料庫級別上有兩個選項控制這種行為:
1.自動建立統計控制優化器是否自動建立列級統計資訊。此選項不影響始終建立的索引級統計資料。預設情況下啟用了自動建立統計資料庫選項。
2.當啟用“自動更新統計資料庫”選項時,SQL Server檢查統計資訊是否在每次編譯或執行查詢時過時,並在需要時對其進行更新。預設情況下,還啟用了自動更新統計資料庫選項。
■提示通過使用STATISTICS_NORECOMPUTE索引選項,可以在索引級別控制統計資訊的自動更新行為。預設情況下,此選項設定為OFF,這意味著統計資訊會自動更新。在索引或表級別更改自動更新行為的另一種方法是使用sp_autostats系統儲存過程。
SQL Server基於影響統計列的INSERT、UPDATE、DELETE和MERGE語句執行的更改數量來確定統計資訊是否過時。SQL Server計算統計列更改了多少次,而不是更改的行數。例如,如果同一行更改了100次,那麼將計數為100次更改,而不是1次更改。
有三種不同的場景,稱為統計更新閾值,有時也稱為統計重新編譯閾值,其中SQL Server將統計標記為過時。
1. 當表為空時,SQL Server會在向表中新增資料時更新統計資料。
2. 當一個表的行數少於500行時,SQL Server在每500個統計列更改之後更新統計資訊。
3. 在SQL Server 2016之前和在資料庫相容級別<130的SQL Server 2016中:當一個表有500行或更多行時,SQL Server在每500+(表中行總數的20%)統計列發生更改後更新統計資訊。
在具有資料庫相容級別=130的SQL Server 2016中:大型表的統計資訊更新閾值是動態的,並且取決於表的大小。表中的行數越多,閾值就越低。在具有數百萬甚至數十億行的大型表中,統計更新閾值可能只是表中總行數的一小部分。這個行為也可以在SQL Server 2008R2 SP1和上面的跟蹤標誌T2371中啟用。
表3-1總結了不同版本的SQL Server中的統計更新閾值行為。
表3-1。統計更新閾值和SQL Server版本
|
在SQL Server 2016之前 |
資料庫相容級別<130的SQL Server 2016 |
具有資料庫相容級別=130的SQL Server 2016 |
預設行為 T2371 |
靜態(~20%)閾值 SQL Server 2008R2 SP1中的動態閾值及其以上 |
靜態(~20%)閾值動態閾值 |
動態閾值 動態閾值(忽略跟蹤標誌) |
這引出了一個非常重要的結論。使用靜態統計資訊更新閾值,觸發統計資訊更新所需的統計資訊列的更改數量與表大小成比例。表越大,自動更新統計資訊的頻率就越低。例如,對於具有10億行的表,需要對統計列執行大約2億次更改以使統計資訊過時。如果可能,建議使用動態更新閾值。
讓我們看看這種行為如何影響我們的系統和執行計劃。此時,表dbo.Books有1265000行。讓我們用字首999向表中新增250000行,如列表3-5所示。在這個示例中,我使用的SQL Server 2012沒有啟用T2371。如果啟用了動態統計更新閾值,則可以看到不同的結果。此外,在SQL Server 2014中引入的新基數估計器也可以改變行為。我們將在本章後面討論。
列表3-5。向dbo.Books新增行
;with Postfix(Postfix) as ( select 100000001 union all select Postfix + 1 from Postfix where Postfix < 100250000 ) insert into dbo.Books(ISBN, Title) select '999-0' + convert(char(9),Postfix) ,'Title for ISBN 999-0' + convert(char(9),Postfix) from Postfix option (maxrecursion 0);
現在,讓我們執行SELECT*FROM dbo.Books WHERE ISBN LIKE'999%'查詢,該查詢選擇具有此類字首的所有行。
如果檢查查詢的執行計劃,如圖3-7所示,您將看到非聚集索引查詢和鍵查詢操作,即使它們在需要從表中選擇將近20%的行的情況下效率很低。
圖3-7。使用999字首選擇行的執行計劃
您還會注意到,在圖3-7中,Index Seek操作符的估計行數和實際行數之間存在巨大的差異。SQL Server估計表中只有31.4行具有字首999,即使有250000行具有這樣的字首。結果,生成了高度低效的計劃。
讓我們通過執行DBCC SHOW_STATISTICS('dbo.Books',IDX_BOOKS_ISBN)命令來檢視IDX_BOOKS_ISBN統計資料。輸出如圖3-8所示。如您所見,儘管我們向表中插入了250000行,但是統計資訊沒有更新,並且字首999的直方圖中沒有資料。第一個結果集中的行數對應於上一次統計更新期間表中的行數。它不包括剛剛插入的250000行。
圖3-8。IDX_BOOKS_ISBN統計
現在,讓我們使用UPDATE STATISTICS dbo.Books IDX_Books_ISBN WITH FULLSCAN命令更新統計資料,然後再次執行SELECT * FROM dbo.Books WHERE ISBN LIKE '999%'查詢。查詢的執行計劃如圖3-9所示。現在估計的行數是正確的,並且SQL Server最終得到一個更高效的執行計劃,該執行計劃使用聚集索引掃描,其I/O讀數比以前減少了約17倍。
圖3-9。統計資訊更新後選擇具有999字首的行的查詢的執行計劃
如您所見,不正確的基數估計可能導致高度低效的執行計劃。過時的統計資料可能是導致基數估計不正確的最常見原因之一。您可以通過檢查執行計劃中估計的和實際的行數來精確地指出其中的一些情況。這兩個值之間的巨大差異常常表明統計是不正確的。更新統計資料可以解決這個問題,並生成更有效的執行計劃。
統計維護
如前所述,SQL Server預設情況下自動更新統計資料。這種行為通常對於小表是可接受的;但是,對於具有數百萬或數十億行的大表,除非使用資料庫相容級別為130或啟用了跟蹤標誌T2371的SQL Server 2016,否則不應該依賴自動統計更新。為了觸發20%的統計資料更新閾值的統計資料更新所需的更改數量將非常高,因此,更新不會被足夠頻繁地觸發。
在這種情況下,建議您手動更新統計資料。在選擇最佳統計維護策略時,必須分析表的大小、資料修改模式和系統可用性。例如,如果系統在營業時間之外沒有沉重的負載,則可以決定每晚更新關鍵表的統計資訊。不要忘記統計和/或索引維護給SQL Server增加了額外的負載。您必須分析它如何影響同一伺服器和/或磁碟陣列上的其他資料庫。
在設計統計維護策略時要考慮的另一個重要因素是如何修改資料。對於鍵值不斷增加或減少的索引,您需要更頻繁地更新統計資訊,例如當索引中最左邊的列被定義為identity或被序列物件填充時。如您所見,如果特定的鍵值在直方圖之外,SQL Server會極大地低估行的數量。這種行為在SQL Server 2014到2016中可能有所不同,我們將在本章後面看到。
可以使用UPDATE STATISTICS命令更新統計資料。當SQL Server更新統計資訊時,它讀取資料的示例而不是掃描整個索引。您可以通過使用FULLSCAN選項來更改該行為,該選項強制SQL Server從索引讀取並分析所有資料。正如您可能猜到的,該選項提供了最準確的結果,儘管在大表的情況下,它可以引入大量的I/O活動。
■註釋SQL Server在重建索引時更新統計資訊。我們將在第6章“索引分段”中更詳細地討論索引維護。
可以使用sp_updatestats系統儲存過程更新資料庫中的所有統計資訊。建議您使用此儲存過程,並在將資料庫升級到新版本的SQL Server之後更新資料庫中的所有統計資料。您應該與DBCC UPDATEUSAGE儲存過程一起執行它,該儲存過程糾正目錄檢視中不正確的頁和行計數資訊。
有一個sys.dm_db_stats_properties DMV,它顯示自上次統計更新以來對統計列所做的修改數量。利用DMV的程式碼如清單3-9所示。
列表3-9。dm_db_stats_properties
select s.stats_id as [Stat ID], sc.name + '.' + t.name as [Table], s.name as [Statistics] ,p.last_updated, p.rows, p.rows_sampled, p.modification_counter as [Mod Count] from sys.stats s join sys.tables t on s.object_id = t.object_id join sys.schemas sc on t.schema_id = sc.schema_id outer apply sys.dm_db_stats_properties(t.object_id,s.stats_id) p where sc.name = 'dbo' and t.name = 'Books';
查詢的結果(如圖3-11所示)表明自上次統計更新以來,對統計列進行了250000次修改。您可以構建一個統計維護例程,該例程定期檢查sys.dm_db_stats_properties DMV,並用大的修改後的計數重新構建統計資訊。
圖3-11。sys.dm_db_stats_properties輸出
另一個與統計資料相關的資料庫選項是自動非同步更新統計資料。預設情況下,當SQL Server檢測到統計資訊過期時,它將暫停查詢執行,同步更新統計資訊,並在統計資訊更新完成後生成新的執行計劃。通過非同步統計資訊更新,SQL Server使用舊的執行計劃執行查詢,該執行計劃基於過時的統計資訊,同時非同步更新後臺中的統計資訊。建議您保持同步統計資訊更新,除非系統具有非常短的查詢超時,在這種情況下,同步統計資訊更新可以超時查詢。
最後,在建立新索引時,SQL Server不會自動刪除列級統計資訊。您應該手動刪除冗餘的列級統計物件。