在理解統計信息(2/6):直方圖中,我們討論了直方圖,密度向量,還有SQL Server如何用統計信息做基數計算(cardinalityestimation)。這篇文章會討論統計信息如何被創建,還有統計信息在性能調優中的重要性。
有2類統計信息,索引統計信息和列統計信息。索引統計信息是索引創建的一部分(建立索引會自動創建索引統計信息)。在where條件列被引用或查詢的group by子句里包含列,列統計信息都會由SQL Server自動創建。
有數據庫屬性設置里,可以設置數據庫是否自動創建統計信息并自動更新統計信息(數據庫屬性->選項->自動)。
自動創建統計信息默認是啟用的,它幫助查詢優化器在需要更好的進行查詢預估時,創建列統計信息。為了更好的性能,建議保留啟用。
自動更新統計信息默認也是啟用的,它幫助查詢優化器在統計信息過期的時候自動更新。當數據有明顯變化時,統計信息就需要更新。這里有個閥限(threshold limit)來標記統計信息是否過期。
自動異步更新統計信息默認是不啟用的。當自動異步更新統計信息被啟用的時候,會有2種方式進行自動更新。異步模式(默認模式),如果統計信息已經過期,查詢優化器會等到計劃生成完成才更新統計信息。同步模式,查詢優化器會初始化統計信息,不會等到計劃的生成完成。通過改變更新統計信息為同步模式可以使性能上一些工作量始終受益。SQL Server在自動創建/更新統計信息的時候,不會進行完全掃描。它只會在可接受的時間內采樣數據來計算統計信息。
在理解統計信息(1/6):密度里,我們看到,當引用的列在group by或where條件里時,統計信息會自動創建。我們來看看當自動創建統計信息關掉的時候,SQL Server如何進行預估。我們運行下面的語句并看看輸出結果。
1 ALTER DATABASE StatisticsDB SET AUTO_CREATE_STATISTICS OFF2 GO3 DROP TABLE SalesOrderDetail_NoStats4 SELECT * INTO SalesOrderDetail_NoStats FROM AdventureWorks2008r2.Sales.SalesOrderDetail5 GO6 SELECT PRoductID,COUNT(*) FROM dbo.SalesOrderDetail_NoStats GROUP BY ProductID7 GO8 EXEC SP_HELPSTATS 'SalesOrderDetail_NoStats', 'ALL'
第1句,我們關掉了StatisticsDB數據庫的自動更新統計信息。第2句,我們創建了salesOrderDetail表的副本。現在我們對ProductId進行group 扮演操作,點擊工具欄的顯示包含實際的執行計劃。
在執行計劃里,我們在表掃描運算符里看到一個黃色的驚嘆號。具體來說,它是警告我們沒有可用的統計信息。在聚集運算符里,我們看到優化器的估計行數是348.306。沒有統計信息,優化器要如何估計行數呢?這里的值是拿記錄總數開方而來。這個表有121317條記錄,如果你對它開方,即,我們就得到348.306。如果你對這個表groupby的其他任何列,預估行數還是一樣的。請注意,自動創建統計信息只控制列統計信息的自動創建。它不控制索引創建時,統計信息的自動創建。
我們來看下統計信息如何影響查詢性能。來看下面2個查詢,記得在最后的查詢語句執行前點擊工具欄的顯示包含實際的執行計劃。
1 USE StatisticsDB 2 GO 3 /* Part 1 WITH AUTO STATS UPDATE ON */ 4 5 ALTER DATABASE StatisticsDB SET AUTO_CREATE_STATISTICS ON 6 ALTER DATABASE StatisticsDB SET AUTO_UPDATE_STATISTICS ON 7 SET STATISTICS IO ON 8 DROP TABLE SalesOrderDetail_NoStats 9 SELECT * INTO SalesOrderDetail_NoStats FROM SalesOrderDetail10 CREATE INDEX ix_productid ON SalesOrderDetail_NoStats (productid)11 UPDATE dbo.SalesOrderDetail_NoStats SET ProductID=775 WHERE SalesOrderDetailID<>112 SELECT * FROM dbo.SalesOrderDetail_NoStats WHERE ProductID=77613 SELECT * FROM dbo.SalesOrderDetail_NoStats WHERE ProductID=77514 15 /* Part 2 WITH AUTO STATS UPDATE Off */16 17 ALTER DATABASE StatisticsDB SET AUTO_CREATE_STATISTICS OFF18 ALTER DATABASE StatisticsDB SET AUTO_UPDATE_STATISTICS OFF19 SET STATISTICS IO ON20 DROP TABLE SalesOrderDetail_NoStats 21 SELECT * INTO SalesOrderDetail_NoStats FROM SalesOrderDetail22 CREATE INDEX ix_productid ON SalesOrderDetail_NoStats (productid)23 UPDATE dbo.SalesOrderDetail_NoStats SET ProductID=775 WHERE SalesOrderDetailID<>124 --Disabling the auto update stats25 ALTER DATABASE StatisticsDB SET AUTO_UPDATE_STATISTICS OFF26 SELECT * FROM dbo.SalesOrderDetail_NoStats WHERE ProductID=77627 SELECT * FROM dbo.SalesOrderDetail_NoStats WHERE ProductID=775
上述2組語句我們都在productid列創建了索引(統計信息也會自動創建),然后我們更新productid為775,只留1條還是不同的productid值。更新后,表里只有2個不同的productid值775和776。第1組語句,我們進行了自動更新統計信息啟用的SELECT查詢。第2組語句我們進行了自動更新統計信息停用的SELECT查詢。我們來看看2者執行計劃和IO統計信息的不同。
我們來看看啟用的執行計劃。第1個where條件是productid=776的查詢估計行數是1,000348,優化器進行的是索引查找。第2個where條件是productid=775的查詢估計行數是121316,優化器選擇的是表掃描,而不是非聚集索引查找和書簽查找。對優化器來說表掃描更有效,相比使用索引查找和書簽查找來獲取表里的所有記錄(只有一條記錄productid是776)。完成這個操作只需要1495個邏輯讀。
我們來看看停用的執行計劃。第1個where條件是productid=776的查詢估計行數是228,優化器進行的是索引查找。這個是基于索引創建是的統計信息來預估的,這個信息在update后已經過期了。第2個where條件是productid=775的查詢估計行數是234,這就嚴重誤導了查詢優化器使用了非聚集掃描和書簽查找來操作,而不是表掃描來獲取表的所有記錄(只有一條記錄productid是776)。完成這個操作需要121710個邏輯讀,相比啟用情況下僅1495個邏輯讀是非常非常高了。
從上面的例子,我們清楚的看到優化器需要更新的統計信息來選擇最優執行計劃,即使你有了必須的索引。在處理性能問題時,我們也需要關注下統計信息。把估計行數與實際行數的區別當作一個好指標,用來深入了解下統計信息,或統計信息的人為更新。
自動更新統計信息可以在以下3個級別進行關閉:
使用sp_autostats存儲過程可以查看表的對應統計信息的自動更新統計信息設置情況。如果在數據級別設置自動更新統計信息為停用,那表級別也會停用。可以使用sp_autostats存儲過程修改表級別的自動更新統計信息設置情況。繼續圍觀理解統計信息(4/6):自動更新統計信息的閥值——人為更新統計信息的重要性。。
新聞熱點
疑難解答