為什麼要寫統計資訊
最近看到園子裡有人寫統計資訊,樓主也來湊熱鬧。話說經常做資料庫的,尤其是做開發的或者優化的,統計資訊造成的效能問題應該說是司空見慣。當然解決辦法也並非一成不變,“一招鮮吃遍天”的做法已經行不通了(題外話:整個時代不都是這樣子嗎)
當然,還是那句話,既然寫了就不能太俗套,寫點不一樣的,本文通過分析一個類似實際案例來解讀統計資訊的更新的相關問題。對於實際問題,不但要解決問題,更重要的是要從理論上深入分析,才能更好地駕馭資料庫。
統計資訊基礎
首先說一個老掉牙的話題,統計資訊的更新閾值:
1,表格從沒有資料變成有大於等於1條資料。
2,對於資料量小於500行的表格,當統計資訊的第一個欄位資料累計變化量大於500以後。
3,對於資料量大於500行的表格,當統計資訊的第一個欄位資料累計變化量大於500 + (20%×表格資料總量)以後。
觸發統計資訊後,rowmodct歸0
關於統計資訊“過期”的問題
下面開始正文,網路上很多關於統計資訊的文章,提到統計資訊,很多都是統計資訊過期的問題,然後跟新之後怎麼怎麼樣。尤其在觸發統計資訊自動更新閾值的第三個區間:也就是說資料累計變化超過20%之後才能自動觸發統計資訊的更新
這一點對於大表來說通常影響是比較大的,比如1000W的表,變化超過20%也+500也就是200W+500行之後才觸發統計資訊更新,這個閾值區間的自動觸發閾值,絕大多數情況是不能接受的,於是對於統計資訊的診斷就變成了是否“過期”
判斷統計資訊是否過期,然後通過更新統計資訊來促使執行計劃更加準確地預估行數,這一點本無可厚非。但是,問題也就出在這裡了:那麼怎麼更新統計資訊?一成不變的做法是否可行,這才是問題的重點。當然肯定有人說,我就是按照預設方式更新的,更新完之後SQL也變得更加優化了什麼的
通過update statistics TableName StatisticName更新某一個索引的統計資訊,或者update statistics TableName更新全表的統計資訊這種情況下往往是小表上可以這麼做,當然對於大表或者小表沒有一個標準值,一切要結合事實來說明問題
下面開始本文的主題:
抽象並簡化出業務中的一個實際案例,建立這麼一張表,類似於訂單和訂單明細表(主子表),這裡你可以想象成是一個訂單表的子表,Id欄位是唯一的,有一個ParentID欄位,是非唯一的,ParentID類似於主表的Id,測試資料按照一個主表Id對應50條子表明細的規律插入資料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
CREATE TABLE [dbo].[TestStaitisticsSample]( [Id] [int] IDENTITY(1,1) NOT NULL, [ParentId] [int] NULL, [OtherColumn] [varchar](50) NULL ) declare @i int=0 while(@i100000000) begin insert into [TestStaitisticsSample](ParentId,OtherColumn)values(@i,NEWID()) /* 中間插入50條,也即一個主表Id對應50條子表明細 */ insert into [TestStaitisticsSample](ParentId,OtherColumn)values(@i,NEWID()) set @i=@i+1 end go create nonclustered index [idx_ParentId] ON [dbo].[TestStaitisticsSample] ( [ParentId] ) go |
本來打算插入1億條的,中間我讓他執行我睡午覺去了,醒來之後發現SSMS掛掉了,掛掉了算了,資料也接近1億了,能說明問題就夠了現在資料分佈的非常明確,就是一個ParentId有50條資料,這一點首先要澄清。
測試資料寫入,以及所建立完成之後來更新 idx_ParentId 索引上的統計資訊,就按照預設的方式來更新,然後來觀察統計資訊
預設方式更新統計資訊(未指定取樣密度)
表裡現在是8000W多一點記錄,預設更新取樣時462239行,那麼這個統計資訊靠譜嗎?
上面說了,造資料的時候,我一個ParentId對應的是50行記錄,這一點非常明確,他這裡給我統計出來的多少?
1,對於取樣的RANG_HI_Key值,比如51632,他給我預估了862.212行
2,對於AVG_RANG_ROW,比如45189到51632之間,他給我預估了6682.490行
這靠譜嗎,這個誤差是無法接受的,很多時候,對於大表,採用預設(未指定取樣密度)的情況下,預設的取樣密度並不足以準確地描述資料分佈情況
指定一個取樣密度的方式更新統計資訊(20%取樣)
這一次用20%的取樣密度,可以看到取樣時15898626行
1,對於取樣的RANG_HI_Key值,比如216305,他給我預估了24.9295行
2,對於AVG_RANG_ROW,比如186302到216305之間,他給我預估了197.4439行
觀察比如上面預設的取樣密度,這一次不管是RANG_HI_Key還是AVG_RANG_ROW得預估,都有不一個非常高的下降,趨於接近於真實的資料分佈(50行)
但是這個誤差還是比較大的,如果繼續提高取樣密度,看看有什麼變化?
指定一個取樣密度的方式更新統計資訊(70%取樣)
這一次用70%的取樣密度,可以看到取樣是55962290行
1,對於取樣的RANG_HI_Key值,比如1978668,他給我預估了71.15906行
2,對於AVG_RANG_ROW,比如1124024到1978668之間,他給我預估了61.89334行
可以說,對於絕大多數值得預估(AVG_RANG_ROW),都愈發接近於真實值
指定一個取樣密度的方式更新統計資訊(100%取樣)
這個就不做過多解釋了,基本上跟真實值是一樣的,只是AVG_RANG_ROW有一點非常非常小的誤差。
取樣密度高低與統計資訊準確性的關係
至於為什麼預設取樣密度和較低取樣密度情況下,誤差很大的情況我簡單解釋一下,也非常容易理解,因為“子表”中儲存主表ID的ParentId值允許重複,在存在重複值的情況下,如果取樣密度不夠,極有可能造成“以偏概全”的情況
比如對10W行資料取樣1W行,原本10W行數劇中有2000個不重複的ParentId值,如果是10%的取樣,在1W行取樣資料中,因為密度不夠大,只找到了20個不重複的ParentId值,那麼就會認為每一行ParentId對應500行資料,這根實際的分佈的每個ParentId有一個非常大的誤差範圍。如果提高取樣密度,那麼這個誤差就會越來越小。
因此在觀察統計資訊是否過期,決定更新統計資訊的時候,一定要注意取樣的密度,就是說表中有多少行資料,統計資訊更新的時候取了多少取樣行,密度有多高。當然,肯定有人質疑,那你說取樣密度越高,也就是取樣行數越高越準確,那麼我就100%取樣。
這樣行不行?還要分情況看,對於幾百萬或者十幾萬的小表來說,當然沒有問題,這也是為什麼資料庫越小,表資料越少越容易掩蓋問題的原因。對於大表,上億的,甚至是十幾億的,你按照100%取樣試一試?
舉個實際例子:
我這裡對一個稍微大一點的表做個全表統計資訊的更新,測試環境,伺服器沒負載,儲存是比普通的機械硬碟要強很多的SAN儲存。採用full scan,也就是100%取樣的更新操作,看一下,僅僅這一樣表的update statistic操作就花費了51分鐘。試想一下,對一個數百GB甚至數TB的庫來說,你敢這麼搞一下。
扯一句,這個中秋節過的,折騰了大半天,話說做測試過程中電腦有開始有點卡,
做完測試之後停掉SQLServer服務,瞬間記憶體釋放了7個G,可見這些個操作還是比較耗記憶體的
總結:
本文通過對於某些場景下,在對較大的表的索引統計資訊更新時,取樣密度的分析,闡述了不同取樣密度下,對統計資訊預估的準確性的影響。當然對於小表,一些都好說。隨著單表資料量的增加,統計資訊的更新策略也要做相應的調整,不光要看統計資訊是否“過期”,更重要的是注意統計資訊更新時究竟取樣了全表的多少行資料做統計。對於大表,採用FULL SCAN或者100%取樣往往是不可行的,這時候就需要做出權衡,做到既能準確地預估,又能夠以合理的代價執行。