惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Y
Y Combinator Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
V
Visual Studio Blog
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
博客园 - 司徒正美
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
L
LangChain Blog

博客园 - nzperfect

PAGELATCH_EX Contention on 2:1:103 SQL Server AlwaysON 同步模式的疑似陷阱 XEvent – SQL Server Log文件对磁盘的写操作大小是多少 SQL Server Log文件对磁盘的写操作大小是多少 Initializing the FallBack certificate failed . TDSSNIClient initialization failed Error after SQL Server 2012 installation: Login Failure for "SQL Server Integration Services 11.0" SSIS service SQL Server 灾难恢复31天之第6天:管理区分配页损坏处理 SQL Server 灾难恢复31天之第5天:处理损坏的非聚集索引 SQL Server 灾难恢复31天之第3天:在还原数据库时确定需要哪些备份文件 SQL Server 灾难恢复31天之第4天:备份 SQL Server 灾难恢复31天之第2天:包含数据库备份在还原时的保护 SQL Server 灾难恢复31天之第1天:DBCC CHECK命令会自动使用已经存在的数据库快照吗? 发布订阅延迟故障排查案例:分发读进程延迟 Query Hint FAST number_rows 改变SQL Server 执行计划 SQL Server 通过界面生成修改列类型脚本时的一个风险(或Bug) 如何完整的修改一个数据库的名称 update值与原值相同时,SQL Server会真的去update还是忽略呢? How can I bring mirror database online after principal server is down ? performance monitor没有SQL Server性能计数器
是什么引起执行计划变得极其糟糕? 应该使用更新统计信息来解...
nzperfect · 2012-12-24 · via 博客园 - nzperfect

考虑这种情况:

在大多数时间里你的存储过程运行良好,但是有时非常差,性能仿佛从天下掉到地下,有人会说肯定是统计信息更新不及时,而且当你手动运行它并查看执行计划,你会发现预估行数和实际行数有很大差距,你会因此而确定是统计信息不准确造成执行计划生成不正确。

但是,可能并不是。。。

存储过程、使用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#