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

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Jina AI
Jina AI
Forbes - Security
Forbes - Security
雷峰网
雷峰网
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
V
Visual Studio Blog
月光博客
月光博客
博客园 - Franky
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享
博客园 - 三生石上(FineUI控件)
酷 壳 – CoolShell
酷 壳 – CoolShell
Apple Machine Learning Research
Apple Machine Learning Research
The Register - Security
The Register - Security
S
SegmentFault 最新的问题
博客园 - 司徒正美
P
Proofpoint News Feed
Know Your Adversary
Know Your Adversary
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
A
Arctic Wolf
Cyberwarzone
Cyberwarzone
Simon Willison's Weblog
Simon Willison's Weblog
U
Unit 42
P
Proofpoint News Feed
Scott Helme
Scott Helme
MyScale Blog
MyScale Blog
T
Tenable Blog
Hugging Face - Blog
Hugging Face - Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
小众软件
小众软件
C
CERT Recently Published Vulnerability Notes
P
Palo Alto Networks Blog
V
V2EX
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tailwind CSS Blog
V
Vulnerabilities – Threatpost
Latest news
Latest news
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
I
Intezer
Microsoft Azure Blog
Microsoft Azure Blog
爱范儿
爱范儿
博客园 - 【当耐特】
B
Blog RSS Feed
N
Netflix TechBlog - Medium
Recent Announcements
Recent Announcements
NISL@THU
NISL@THU
C
Cisco Blogs
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Schneier on Security

博客园 - 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#