算符列在物理运算符之后,两者均位于工具提示的顶部。 估计行大小 估计I/O开销 估计CPU开销 估计运算符开销 操作符生成的行的估计大小(字节)。 用于执行操作的所有 I/O 活动的估计开销。此值应尽可能低。 用于执行操作的所有 CPU 活动的估计开销。 用于执行此操作的查询优化器的开销。此操作的开销以占查询总开销的百分比的形式显示在括号中。由于查询引擎选择最高效的操作来执行查询或执行语句,因此此值应尽可能低。 查询优化器执行此操作及同一子树内位于此操作之前的所有操作的总开销。 运算符生成的行数。 估计子树大小 估计行数 参考:
显示图形执行计划
http://msdn.microsoft.com/zh-cn/library/ms178071(v=SQL.100).aspx
3.4. 分析SQL语句
前面已经提到影响脚本最大的两个指标是占用CPU及I/O资源,那么我们分析脚本也就是要找出占用两项资源比较大的查询。在这里只介绍两种分析脚本的方法:分析执行计划及通过io选项分析I/O资源。
3.4.1. 分析执行计划
执行计划主要用于显示查询的运算方法及脚本的开销,所以分析执行计划主要从两方面入手,即:节点开销和节点的物理运算。节点的开销越高说明对脚本性能的影响就越大,所以分析执行计划分析开销比较大的节点,然后改善当前节点的运算方法。以下图为例:
在上图的执行计划中节点“[exam_paper_node].[PK_exam_paper_node]”占用95%的开销,很显然这个节点是这个SQL语句的最大瓶颈。节点的运算方式为“Clustered Index Scan”,而此运算方式的效率比较差,那么需要我们参加索引改进运算方式。
当前改进运算方式并不是只有添加索引一种方法,如:减少嵌套查询(嵌套查询无法有效利用索引)、使用Left Join 代替In/Not In 等等、调用语句结构引导数据库采用其它的执行计划。
除了数据库自动产生执行计划之外,我们也可以手工反指定查询计划。但是不建议这样做,因为数据库的选择基本上都是正确的。
3.4.2. IO选项
在数据库中通过set statistics io on方式开启I/O选项,一般来说此选项常常和set statistics time on选项一起使用。Time选项用于显示脚本执行所占用的CPU时间。下图为显示此选项显示的结果:
I/O选项的输出结果 输出项 表 扫描计数 逻辑读取 物理读取 预读 Lob 逻辑读取 Lob物理读取 Lob预读 表的名称。 含义 在查询中涉及到的表被访问的次数,这个数据比较重要,我们需要尽可能的减少表读扫描的次数。 从数据缓存读取的页数。 这个是最重要的数据了,可以看作我们性能优化的重要指标。 从磁盘读取的页数。 把数据从磁盘读取到数据缓冲区。 为进行查询而放入缓存的页数。 从数据缓存读取的 text、ntext、image 或大值类型 (varchar(max)、nvarchar(max)、varbinary(max)) 页的数目。 从磁盘读取的 text、ntext、image 或大值类型页的数目。 为进行查询而放入缓存的 text、ntext、image 或大值类型页的数目。
Time选项的输出结果
? 第一个执行时间:表示数据库解析脚本,并把解析的结果放到过程缓冲区所
花费的时间
? 第二个执行时间:表示数据从过程缓冲区中取出解析结果供执行的时间,大
多数情况下这二个值都会是0,因为这个过程执行得相当地快。
4. 优化SQL脚本
对SQL脚本的优化就是减少脚本对CPU及I/O资源的占用,前面我们已经提到通过执行计划和I/O选项可以简单分析出脚本的性能瓶颈。针对各种各样的性能瓶颈也有很不同的优化方法,在本文档中只讨论两种方式:优化索引和调整脚本结构。
4.1. 优化索引
优化索引这也是最简单最常用的脚本优化方法,我们分析出脚本中缺少的索引,通过添加缺失的索引优化数据库。
在前面的示例我们通过执行计划及
I/O
选项得知
“[exam_paper_node].[PK_exam_paper_node]”的节点是性能瓶颈。它是通过paper_uid查找paper_node_uid,那么我们就可以在表exam_paper_node添加索引“index_paper_uid”,把paper_uid作为索引键,并把paper_node_uid添加到包含性列中。那么我们添加脚本之后的执行计划如下:
I/O及CPU资源占用如下:
4.2. 调整脚本结构
调整脚本结构包含的内容就比较多,但是不管怎么样变化它的目的就是为了提高索引的利用及使数据以更优的执行计划执行查询。下面我们拿一个简单的示例说明下:
select * from (
select row_number() over (order by exam_code desc) as
rowId,exam_exam.*,exam_arrange.exam_arrange_uid,exam_arrange.begin_time,exam_arrange.end_time,exam_grade_account.attend_times,exam_arrange.is_examination from exam_exam with (nolock)
inner join exam_arrange with (nolock) on inner join nv_folder with (nolock) on inner join (
select distinct exam_user.exam_arrange_uid from exam_user with (nolock) where exam_user.owner_uid in
exam_exam.exam_uid=exam_arrange.exam_uid exam_exam.folder_uid=nv_folder.folder_uid
(N'FC4BC3E0-ECB0-47C6-86EC-A4062C2A955C',N'49e37ca0-503a-4470-aa0f-2a2e83436cab',N'71eebecc-0129-4697-999b-79fa1bbc5fd6|68639fa3-fd11-4831-a8a4-9fae4c2d95f6|4216eb73-1375-4165-9441-3776050ff4a6|4fd9916c-66c2-4955-8d39-8c033185bf59|81cdbef2-6804-4ba6-ac1d-d8e5b3ac0b9f|bbae8e98-33f4-4464-9ee3-c4947021a7f8|49e37ca0-503a-4470-aa0f-2a2e83436cab',N'71eebecc-0129-4697-999b-79fa1bbc5fd6',N'68639fa3-fd11-4831-a8a4-9fae4c2d95f6',N'4216eb73-1375-4165-9441-3776050ff4a6',N'4fd9916c-66c2-4955-8d39-8c033185bf59',N'81cdbef2-6804-4ba6-ac1d-d8e5b3ac0b9f',N'bbae8e98-33f4-4464-9ee3-c4947021a7f8',N'49e37ca0-503a-4470-aa0f-2a2e83436cab',N'C70C0CA2-2EF5-4C53-9F58-C3F740334EB6',N'71eebecc-0129-4697-999b-79fa1bbc5f19')
) exam_user on
exam_arrange.exam_arrange_uid=exam_user.exam_arrange_uid
left join (
select exam_uid,count(exam_grade_uid) as attend_times from exam_grade with (nolock) where is_examination = 'N' and group by exam_uid ) exam_grade_account on
user_uid=N'FC4BC3E0-ECB0-47C6-86EC-A4062C2A955C'
exam_exam.exam_uid=exam_grade_account.exam_uid
where
exam_arrange.begin_time and (exam_arrange.end_time>N'2012-05-09 10:47:03' or and (exam_exam.exam_class_code = N'exam') and exam_arrange.end_time is null) (exam_exam.exam_status_code = N'normal') and (exam_arrange.is_examination = N'N') ) as t where rowId between 1 and 10 上面的脚本代码是用于查询考生可参加的考试列表,那么它在数据库中执行的情况如下图所示: 可以看出脚本的执行效率非常差,特别是对表’exam_user’的读取次数过多。而且通过执行计划来看对表exam_user执行的是“Clustered Index Scan”操作,效率非常低下。针对上面的SQL语句我们通过两个步骤进行优化: 第一步调整脚本结构减少脚本中的嵌套查询。在脚本中有两个嵌套查询针对exam_user和exam_grade,那么我们先建立两个表变量分表用于存储exam_user和exam_grade中结果。 第二步针对优化之后脚本结构添加缺失的索引,主要是添加exam_user的owner_uid为索引、exam_arrange的exam_uid为索引。 那么优化之后整个SQL语句的开销不会超过30毫秒。具体优化之后的脚本代码见附件。本次优化中主要使用的表变量,使用时需要注意表变量是放到内存中的,表变量内数据量不能超过1000条,否则性能会下降。 5. 附件 获取考生考试列表优化之后的脚本 declare @T1 table ( exam_uid varchar(36), attend_times int, primary key (exam_uid) ) declare @T2 table (