通過手動建立統計資訊優化sql查詢效能案例
本質原因在於:SQL Server 統計資訊只包含複合索引的第一個列的資訊,而不包含複合索引資料組合的資訊
來源於工作中的一個實際問題,
這裡是組合列資料不均勻導致查詢無法預估資料行數,從而導致無法選擇合理的執行計劃導致效能低下的情況
我這裡把問題簡單化,主要是為了說明問題
如下一張業務表,主要看兩個“狀態”欄位,BusinessStatus1 和 BusinessStatus2 create table BusinessTable ( Id int identity(1,1), Col2 varchar(50), Col3 varchar(50), Col4 varchar(50), BusinessStatus1 tinyint, BusinessStatus2 tinyint, CreateDate Datetime ) GO --向測試表中寫入資料: begin tran declare @i int set @i=0 while @i<500000 begin insert into BusinessTable values (NEWID(),NEWID(),NEWID(),1,10,GETDATE()-RAND()*1000)insert into BusinessTable values (NEWID(),NEWID(),NEWID(),1,20,GETDATE()-RAND()*1000) insert into BusinessTable values (NEWID(),NEWID(),NEWID(),1,30,GETDATE()-RAND()*1000) insert into BusinessTable values (NEWID(),NEWID(),NEWID(),2,20,GETDATE()-RAND()*1000) insert into BusinessTable values(NEWID(),NEWID(),NEWID(),2,30,GETDATE()-RAND()*1000) insert into BusinessTable values (NEWID(),NEWID(),NEWID(),2,40,GETDATE()-RAND()*1000) insert into BusinessTable values (NEWID(),NEWID(),NEWID(),3,30,GETDATE()-RAND()*1000) insert into BusinessTable values (NEWID(),NEWID(),NEWID(),3,40,GETDATE()-RAND()*1000) insert into BusinessTable values (NEWID(),NEWID(),NEWID(),3,50,GETDATE()-RAND()*1000) set @i=@i+1 end commit --插入一條特殊資料,也就是實際業務場景中: insert into BusinessTable values (NEWID(),NEWID(),NEWID(),3,10,GETDATE()-RAND()*1000)
--測試資料的特點是: --BusinessStatus1 的分佈位:1,2,3, --BusinessStatus2 的分佈位:10,20,30,40,50 --目前資料的對應關係, --但是注意插入的一條特殊資料: --BusinessStatus1 和 BusinessStatus2 的組合為:BusinessStatus1=3 and BusinessStatus2=10,在451W條資料中是唯一的一個組合 --建立如下索引: Create Clustered index idx_createDate on BusinessTable(CreateDate) Create Index idx_status on BusinessTable(BusinessStatus1,BusinessStatus2)
進行如下查詢,就是查詢那條所謂的特殊資料
select * from BusinessTable where BusinessStatus1=3 and BusinessStatus2=10
發現執行計劃如下:走的是全表掃描,IO代價也不小,
這種情況下,明明只有一條資料,卻要走全表掃描
(實際業務中類似資料也不僅只有一條這麼巧,但是在千萬級的表中,符合類似條件的資料很少,
打個比方好理解一點,就像訂單表一樣,訂單是退訂狀態,且尚未退款,這種資料的分佈是少之又少吧
只是舉例,不要較真)
上面查詢的IO資訊
再通過強制索引提示的情況下,發現同樣的查詢,IO有一個非常大的下降
分析上述sql為什麼不走索引?因為畢竟符合條件的資料只有一條,走全表掃描代價也過於大了,尤其是實際情況中,業務表更大,邏輯也沒有這麼直白
這個還要從索引統計資訊說起,在符合索引中,索引統計資訊只是統計前導列的,對於組合列的分佈,sqlserver是無法預估到的,這一點可以通過第一個查詢的執行計劃發現
sqlserver只是能夠預估到 BusinessStatus1 =3 的情況下的資料分佈,但是無法預估到 BusinessStatus1=3 and BusinessStatus2=10這個組合情況下的資料分佈情況
當然通過統計資訊也可以看到,統計資訊只記錄了BusinessStatus1的列的資料分佈情況,但是實際執行的過程中,無法預估BusinessStatus1=3 and BusinessStatus2=10的準確分佈
找到了問題的原因,就容易解決了,既然sqlserver無法預估到BusinessStatus1=3 and BusinessStatus2=10這個組合條件的資料分佈請,
那麼就建立一個過濾統計資訊,讓sqlserver準確地知道這個條件下資料的分佈請,就容易做出相對準確的執行計劃了
通過如下語句,建立一個該條件的統計資訊
create statistics BusinessTableFilterStatistics on BusinessTable(BusinessStatus1,BusinessStatus2) where BusinessStatus1=3 and BusinessStatus2=10 --建立完統計資訊之後注意要做個更新 UPDATE STATISTICS BusinessTable BusinessTableFilterStatistics with fullscan
建立完統計資訊之後,發現表上會增加一個剛剛建立的統計資訊
現在再來看這個查詢的執行計劃情況,發現其按照預期的走了索引
同時觀察起IO情況,也有一個大幅度的下降
總結:
以上通過手動建立統計資訊,來促使sqlserver在生成執行計劃的時候,準確地知道資料的分佈情況,做出較為優化的執行計劃,在某些特殊的情況下,可以作為優化的一個考慮方向
後記:
或許有人認為這個問題該歸結於parameter sniff的問題,其實這個問題跟parameter sniff還不太一樣(當然也有一點像)
通常情況下,所說的parameter sniff問題是單列資料分佈不均勻的情況下,因為執行計劃重用導致效能地下的一個現象,重點是執行計劃的不合理重用
這裡的問題在於,由於統計資訊的資料計算方式,sqlserver 壓根無法預估到符合條件資料的準確分佈,從而無法做出合理的執行計劃的情況
當然這種情況也比較特殊,在強制索引提示以外,可以通過手動建立統計資訊來達到優化的目的