是什么引起執行計划變得極其糟糕? 應該使用更新統計信息來解決它嗎?


考慮這種情況:

在大多數時間里你的存儲過程運行良好,但是有時非常差,性能仿佛從天下掉到地下,有人會說肯定是統計信息更新不及時,而且當你手動運行它並查看執行計划,你會發現預估行數和實際行數有很大差距,你會因此而確定是統計信息不准確造成執行計划生成不正確。

但是,可能並不是。。。

存儲過程、使用sp_executesql的參數化語句、預編譯的SQL語句都會重用一個緩存的執行計划,它是由一個稱為參數嗅探定義的,參數嗅探本身並沒有問題,但是相同的存儲過程或參數化語句去調用已經生成的執行計划時,就有可能引發一些問題。比如:如果一個參數化查詢語句只返回一行數據,那么它可能會生成一個簡單的輕量級執行計划,這個執行計划被緩存,它可能僅是一個非聚集索引查找+書簽查找,但是,如果后面有一個查詢返回大量的數據行,那么之前生成的執行計划可能就不適合了。由於執行計划緩存在內存中,有很多原因可能會從內存中消失,如果它剛消失,而這時過來一個使用頻率極少的查詢正好返回大量的數據行,那么這時生成的執行計划就會被緩存,這之后的查詢哪怕只返回一行,也會用之前剛緩存的執行計划,這將會導致性能很差。

例如:

use tempdb
go
--drop table tb_1
create table tb_1 (id int primary key identity,name varchar(200),dt datetime default getdate(),xx int)
declare @i int =1
while @i<10000
begin
insert into tb_1 (name) select LTRIM(@i)
set @i+=1
end

create index ix_name on tb_1 (name)
go
insert into tb_1 (name) select '99999'
go 5000

exec sp_executesql N'select top 10 * from tb_1 where name=@name order by dt desc',N'@name varchar(20)',@name='1'

IO情況:表 'tb_1'。掃描計數 1,邏輯讀取 4 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。
執行計划:

正常情況下,這個name='1'查詢只會返回一行,而且當name變化成其它值時,絕大多數情況也只是返回少量的行,這時候如果name有索引,它就只是一個索引查找+Key查找,它會非常的快,從上面看到只有4個page.

 

假如有一個極少數name=99999會返回大量的行,它應該用聚集索引掃描會更好,但由於SQL Server的參數嗅探,所以它會使用之前緩存的執行計划,這就會使效率變得比較差了,但是由於很少去執行這個name=99999,這樣看起來還可以接受,如下圖:

exec sp_executesql N'select top 10 * from tb_1 where name=@name order by dt desc',N'@name varchar(20)',@name='99999'

IO情況:表 'tb_1'。掃描計數 1,邏輯讀取 10014 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。
執行計划:


如果不使用緩存的執行計划,會是什么樣的呢?

select top 10 * from tb_1 where name='99999' order by dt desc

IO情況:表 'tb_1'。掃描計數 1,邏輯讀取 64 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。
執行計划:

 


然而,如果此時緩存中沒有該語句的執行計划,或者它可能剛剛從內存中被清除出去,那么此時執行name=99999將會生成一個新的執行計划並緩存,也就是上面這個T-sql產生的Clustered Index Scan的執行計划,它是聚集索引掃描,在這種情況之后,其它的后續的只返回1行數據的大量查詢,都會重用這個新的執行計划,性能都會很差了,如:

這就是開篇所述的情況,此時你查看上面的執行計划,你會發現預估行數和實際行數有很大的差距,那么它是由於統計信息不及時造成的嗎?顯然不是。



但是如果你不知道上面這個原因,一直認為是統計信息問題,你會怎么做呢?你會UPDATE STATISTICS tablename或者UPDATE STATISTICS tablename indexname
然后你再次執行有問題的存儲過程或參數化語句,結果它的執行計划正確了,那么你更堅定的認為是統計信息問題了。

然而,更新統計信息是有副作用的,會使查詢優化器重新編譯相關的語句,並可能會產生其它新的問題。

那么應該如何做呢?
如果是存儲過程,那么我們應該重新編譯它: sp_recompile procedurename
如果是參數化語句,那么我們找出它的行計划,清除它:
SELECT  *
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
WHERE Objtype in ('Prepared') and text like '%select top 10 * from tb_1 where name=@name order by dt desc%'
查到plan_handle為0x060002006E0F721540E14B8B000000000000000000000000
然后執行:DBCC FREEPROCCACHE (0x060002006E0F721540E14B8B000000000000000000000000); 

如果如此操作之后並未生成一個新的執行計划,那么再考慮更新統計信息。



更新統計信息一定會造成執行計划重新編譯嗎?no
1.在SQL Server 2005, 2008 and 2008R2版本中,如果auto update statistics開啟(默認),那么會造成執行計划重新編譯。
2.而在SQL Server 2012中不管auto update statistics是否開啟,更新統計信息都不會影響執行計划。

Kimberly反饋給微軟這個改變是否是SQL 2012的一個Bug,從微軟回復來看,這個是改變是正確的,而在之前版本上的行為才是一個真正的Bug. -_- !!!

本文參考:
http://www.sqlskills.com/blogs/kimberly/post/what-caused-that-plan-to-go-horribly-wrong-should-you-update-statistics.aspx
https://connect.microsoft.com/SQLServer/feedback/details/769338/update-statistics-does-not-cause-plan-invalidation#


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM