SqlServer查询计划



当时我在填充测试数据时,故意把CategoryId分为1到8(10是后来临时加的),每组填充了78条数据。所以【索引统计信息】的第三个表格的数据也都是正确的, 也正是根据这些统计信息,SQL Server才能对每个执行步骤预估相应的数据量,从而影响Join之类的选择。当然了,在选择Join方式时, 也要参考第二个表格中字段的选择性。SQL Server在为查询生成执行计划时, 查询优化器将使用这些统计信息并结合相关的索引来评估每种方案的开销来选择最佳的查询计划。

再来个例子说明一下统计信息对于查询计划的重要性。首先多加点数据,请看以下代码:

declare @newCategoryId int;
insert into dbo.Categories (CategoryName) values(N'Test statistics');
set @newCategoryId = scope_identity();

declare @count int;
set @count = 0;

while( @count < 100000 )
begin
    insert into Products (ProductName, CategoryID, Unit, UnitPrice, Quantity, Remark) 
    values( cast(newid() as nvarchar(50)), @newCategoryId, N'个', 100, @count +1, N'');

    set @count = @count + 1;
end
go

update statistics Products;
go

再来看看索引统计信息:

再来看看同一个查询,但因为查询参数值不同时,SQL Server选择的执行计划:

select p.ProductId, t.Quantity 
from Products as p left outer join [Order Details] as t on p.ProductId = t.ProductId 
where p.CategoryId = 26;    -- 26 就是最新产生的CategoryId,因此这个查询会返回10W条记录

select p.ProductId, t.Quantity 
from Products as p left outer join [Order Details] as t on p.ProductId = t.ProductId 
where p.CategoryId = 6;    -- 这个查询会返回95条记录

从上图可以看出,由于CategoryId的参数值不同,SQL Server会选择完全不同的执行计划。统计信息重要性在这里体现的很清楚吧。

创建统计信息后,数据库引擎对列值(根据这些值创建统计信息)进行排序, 并根据这些值(最多 200 个,按间隔分隔开)创建一个“直方图”。直方图指定有多少行精确匹配每个间隔值, 有多少行在间隔范围内,以及间隔中值的密度大小或重复值的发生率。

SQL Server 2005 引入了对 char、varchar、varchar(max)、nchar、nvarchar、nvarchar(max)、text 和 ntext 列创建的统计信息收集的其他信息。这些信息称为“字符串摘要”,可以帮助查询优化器估计字符串模式中查询谓词的选择性。 查询中有 LIKE 条件时,使用字符串摘要可以更准确地估计结果集大小,并不断优化查询计划。 这些条件包括诸如 WHERE ProductName LIKE '%Bike' 和 WHERE Name LIKE '[CS]heryl' 之类的条件。

既然【索引统计信息】这么重要,那么它会在什么时候生成或者更新呢?事实上,【索引统计信息】是不用我们手工去维护的, SQL Server会自动去维护它们。而且在SQL Server中也有个参数来控制这个更新方式:

统计信息自动功能工作方式

创建索引时,查询优化器自动存储有关索引列的统计信息。另外,当 AUTO_CREATE_STATISTICS 数据库选项设置为 ON(默认值)时, 数据库引擎自动为没有用于谓词的索引的列创建统计信息。

随着列中数据发生变化,索引和列的统计信息可能会过时,从而导致查询优化器选择的查询处理方法不是最佳的。 例如,如果创建一个包含一个索引列和 1,000 行数据的表,每一行在索引列中的值都是唯一的, 则查询优化器将把该索引列视为收集查询数据的好方法。如果更新列中的数据后存在许多重复值, 则该列不再是用于查询的理想候选列。但是,查询优化器仍然根据索引的过时分布统计信息(基于更新前的数据),将其视为好的候选列。

当 AUTO_UPDATE_STATISTICS 数据库选项设置为 ON(默认值)时,查询优化器会在表中的数据发生变化时自动定期更新这些统计信息。 每当查询执行计划中使用的统计信息没有通过针对当前统计信息的测试时就会启动统计信息更新。 采样是在各个数据页上随机进行的,取自表或统计信息所需列的最小非聚集索引。 从磁盘读取一个数据页后,该数据页上的所有行都被用来更新统计信息。 常规情况是:在大约有 20% 的数据行发生变化时更新统计信息。但是,查询优化器始终确保采样的行数尽量少。 对于小于 8 MB 的表,则始终进行完整扫描来收集统计信息。

采样数据(而不是分析所有数据)可以将统计信息自动更新的开销降至最低。 在某些情况下,统计采样无法获得表中数据的精确特征。可以使用 UPDATE STATISTICS 语句的 SAMPLE 子句和 FULLSCAN 子句, 控制按逐个表的方式手动更新统计信息时采样的数据量。FULLSCAN 子句指定扫描表中的所有数据来收集统计信息, 而 SAMPLE 子句用来指定采样的行数百分比或采样的行数

在 SQL Server 2005 中,数据库选项 AUTO_UPDATE_STATISTICS_ASYNC 提供了统计信息异步更新功能。 当此选项设置为 ON 时,查询不等待统计信息更新,即可进行编译。而过期的统计信息置于队列中, 由后台进程中的工作线程来更新。查询和任何其他并发查询都通过使用现有的过期统计信息立即编译。 由于不存在等待更新后的统计信息的延迟,因此查询响应时间可预测;但是过期的统计信息可能导致查询优化器选择低效的查询计划。 在更新后的统计信息就绪后启动的查询将使用那些统计信息。这可能会导致重新编译缓存的计划(取决于较旧的统计信息版本)。 如果在同一个显式用户事务中出现某些数据定义语言 (DDL) 语句(例如,CREATE、ALTER 和 DROP 语句),则无法更新异步统计信息。

AUTO_UPDATE_STATISTICS_ASYNC 选项设置于数据库级别,并确定用于数据库中所有统计信息的更新方法。 它只适用于统计信息更新,而无法用于以异步方式创建统计信息。只有将 AUTO_UPDATE_STATISTICS 设置为 ON 时, 将此选项设置为 ON 才有效。默认情况下,AUTO_UPDATE_STATISTICS_ASYNC 选项设置为 OFF。

从以上说明中,我们可以看出,对于大表,还是有可能存在统计信息更新不及时的时候,这时,就可能会影响查询优化器的判断了。
有些人可能有个经验:对于一些慢的查询,他们会想到重建索引来尝试解决。其实这样做是有道理的。 因为,在某些时候一个查询突然变慢了,可能和统计信息更新不及时有关,进而会影响查询优化器的判断。 如果此时重建索引,就可以让查询优化器知道最新的数据分布,自然就可以避开这个问题。 还记得我前面用【set statistics profile on】显示的执行过程表格吗?注意哦,那个表格就显示每个步骤的实际数据量和预估的数据量。要不要重建索引,其实我们可以用【set statistics profile on】来看一下,如果实际数据量和预估的数据量的差值比较大, 那么我们可以考虑手工去更新统计信息,然后再去试试。

优化视图查询

再来说说优化视图查询,虽然视图也是由一个查询语句定义的,本质上也是一个查询,但它和一般的查询语句在优化时,还是有所区别的。 这里主要的区别在于,视图虽然是由一个查询语句定义的,但如果只去分析这个查询定义,可能得到的意义不大,因为视图多数时候就不是直接使用, 而是在使用前,会加上where语句,或者放在其它语句中供from子句所使用。下面还是举个例子吧,在我的演示数据库中有个视图OrdersView,定义代码前面有。 我们来看看,如果直接使用这个视图,会有什么样的执行计划出来:

从这个视图可以看出,SQL Server会对表Orders做全表扫描,应该是很低效的。再来看看下面这个查询:

从这个执行计划可以看出,与上面那个就不一样了。前一个查询中对Orders表的查找是使用【Clustered Index Scan】的方式, 而现在在使用【Clustered Index Seek】的方式了,最右边二个步骤的成本的百分比也发生了改变。这样就足以说明,优化视图时, 最好能根据实际需求,应用不同的过滤条件,再来决定如何去优化。

再来一个由三个查询组成的情况来看看这个视图的执行计划。

select * from dbo.OrdersView where OrderId = 1;
select * from dbo.OrdersView where CustomerId = 1;
select * from dbo.OrdersView where OrderDate >= '2010-12-1' and OrderDate < '2011-12-1';

很明显,对于同一个视图,在不同的过滤条件下,执行计划的差别很明显。

推荐阅读-MSDN文章

索引统计信息
http://msdn.microsoft.com/zh-cn/library/ms190397(SQL.90).aspx

查询优化建议
http://msdn.microsoft.com/zh-cn/library/ms188722(SQL.90).aspx

用于对运行慢的查询进行分析的清单
http://msdn.microsoft.com/zh-cn/library/ms177500(SQL.90).aspx

逻辑运算符和物理运算符引用
http://msdn.microsoft.com/zh-cn/library/ms191158(SQL.90).aspx


当时我在填充测试数据时,故意把CategoryId分为1到8(10是后来临时加的),每组填充了78条数据。所以【索引统计信息】的第三个表格的数据也都是正确的, 也正是根据这些统计信息,SQL Server才能对每个执行步骤预估相应的数据量,从而影响Join之类的选择。当然了,在选择Join方式时, 也要参考第二个表格中字段的选择性。SQL Server在为查询生成执行计划时, 查询优化器将使用这些统计信息并结合相关的索引来评估每种方案的开销来选择最佳的查询计划。

再来个例子说明一下统计信息对于查询计划的重要性。首先多加点数据,请看以下代码:

declare @newCategoryId int;
insert into dbo.Categories (CategoryName) values(N'Test statistics');
set @newCategoryId = scope_identity();

declare @count int;
set @count = 0;

while( @count < 100000 )
begin
    insert into Products (ProductName, CategoryID, Unit, UnitPrice, Quantity, Remark) 
    values( cast(newid() as nvarchar(50)), @newCategoryId, N'个', 100, @count +1, N'');

    set @count = @count + 1;
end
go

update statistics Products;
go

再来看看索引统计信息:

再来看看同一个查询,但因为查询参数值不同时,SQL Server选择的执行计划:

select p.ProductId, t.Quantity 
from Products as p left outer join [Order Details] as t on p.ProductId = t.ProductId 
where p.CategoryId = 26;    -- 26 就是最新产生的CategoryId,因此这个查询会返回10W条记录

select p.ProductId, t.Quantity 
from Products as p left outer join [Order Details] as t on p.ProductId = t.ProductId 
where p.CategoryId = 6;    -- 这个查询会返回95条记录

从上图可以看出,由于CategoryId的参数值不同,SQL Server会选择完全不同的执行计划。统计信息重要性在这里体现的很清楚吧。

创建统计信息后,数据库引擎对列值(根据这些值创建统计信息)进行排序, 并根据这些值(最多 200 个,按间隔分隔开)创建一个“直方图”。直方图指定有多少行精确匹配每个间隔值, 有多少行在间隔范围内,以及间隔中值的密度大小或重复值的发生率。

SQL Server 2005 引入了对 char、varchar、varchar(max)、nchar、nvarchar、nvarchar(max)、text 和 ntext 列创建的统计信息收集的其他信息。这些信息称为“字符串摘要”,可以帮助查询优化器估计字符串模式中查询谓词的选择性。 查询中有 LIKE 条件时,使用字符串摘要可以更准确地估计结果集大小,并不断优化查询计划。 这些条件包括诸如 WHERE ProductName LIKE '%Bike' 和 WHERE Name LIKE '[CS]heryl' 之类的条件。

既然【索引统计信息】这么重要,那么它会在什么时候生成或者更新呢?事实上,【索引统计信息】是不用我们手工去维护的, SQL Server会自动去维护它们。而且在SQL Server中也有个参数来控制这个更新方式:

统计信息自动功能工作方式

创建索引时,查询优化器自动存储有关索引列的统计信息。另外,当 AUTO_CREATE_STATISTICS 数据库选项设置为 ON(默认值)时, 数据库引擎自动为没有用于谓词的索引的列创建统计信息。

随着列中数据发生变化,索引和列的统计信息可能会过时,从而导致查询优化器选择的查询处理方法不是最佳的。 例如,如果创建一个包含一个索引列和 1,000 行数据的表,每一行在索引列中的值都是唯一的, 则查询优化器将把该索引列视为收集查询数据的好方法。如果更新列中的数据后存在许多重复值, 则该列不再是用于查询的理想候选列。但是,查询优化器仍然根据索引的过时分布统计信息(基于更新前的数据),将其视为好的候选列。

当 AUTO_UPDATE_STATISTICS 数据库选项设置为 ON(默认值)时,查询优化器会在表中的数据发生变化时自动定期更新这些统计信息。 每当查询执行计划中使用的统计信息没有通过针对当前统计信息的测试时就会启动统计信息更新。 采样是在各个数据页上随机进行的,取自表或统计信息所需列的最小非聚集索引。 从磁盘读取一个数据页后,该数据页上的所有行都被用来更新统计信息。 常规情况是:在大约有 20% 的数据行发生变化时更新统计信息。但是,查询优化器始终确保采样的行数尽量少。 对于小于 8 MB 的表,则始终进行完整扫描来收集统计信息。

采样数据(而不是分析所有数据)可以将统计信息自动更新的开销降至最低。 在某些情况下,统计采样无法获得表中数据的精确特征。可以使用 UPDATE STATISTICS 语句的 SAMPLE 子句和 FULLSCAN 子句, 控制按逐个表的方式手动更新统计信息时采样的数据量。FULLSCAN 子句指定扫描表中的所有数据来收集统计信息, 而 SAMPLE 子句用来指定采样的行数百分比或采样的行数

在 SQL Server 2005 中,数据库选项 AUTO_UPDATE_STATISTICS_ASYNC 提供了统计信息异步更新功能。 当此选项设置为 ON 时,查询不等待统计信息更新,即可进行编译。而过期的统计信息置于队列中, 由后台进程中的工作线程来更新。查询和任何其他并发查询都通过使用现有的过期统计信息立即编译。 由于不存在等待更新后的统计信息的延迟,因此查询响应时间可预测;但是过期的统计信息可能导致查询优化器选择低效的查询计划。 在更新后的统计信息就绪后启动的查询将使用那些统计信息。这可能会导致重新编译缓存的计划(取决于较旧的统计信息版本)。 如果在同一个显式用户事务中出现某些数据定义语言 (DDL) 语句(例如,CREATE、ALTER 和 DROP 语句),则无法更新异步统计信息。

AUTO_UPDATE_STATISTICS_ASYNC 选项设置于数据库级别,并确定用于数据库中所有统计信息的更新方法。 它只适用于统计信息更新,而无法用于以异步方式创建统计信息。只有将 AUTO_UPDATE_STATISTICS 设置为 ON 时, 将此选项设置为 ON 才有效。默认情况下,AUTO_UPDATE_STATISTICS_ASYNC 选项设置为 OFF。

从以上说明中,我们可以看出,对于大表,还是有可能存在统计信息更新不及时的时候,这时,就可能会影响查询优化器的判断了。
有些人可能有个经验:对于一些慢的查询,他们会想到重建索引来尝试解决。其实这样做是有道理的。 因为,在某些时候一个查询突然变慢了,可能和统计信息更新不及时有关,进而会影响查询优化器的判断。 如果此时重建索引,就可以让查询优化器知道最新的数据分布,自然就可以避开这个问题。 还记得我前面用【set statistics profile on】显示的执行过程表格吗?注意哦,那个表格就显示每个步骤的实际数据量和预估的数据量。要不要重建索引,其实我们可以用【set statistics profile on】来看一下,如果实际数据量和预估的数据量的差值比较大, 那么我们可以考虑手工去更新统计信息,然后再去试试。

优化视图查询

再来说说优化视图查询,虽然视图也是由一个查询语句定义的,本质上也是一个查询,但它和一般的查询语句在优化时,还是有所区别的。 这里主要的区别在于,视图虽然是由一个查询语句定义的,但如果只去分析这个查询定义,可能得到的意义不大,因为视图多数时候就不是直接使用, 而是在使用前,会加上where语句,或者放在其它语句中供from子句所使用。下面还是举个例子吧,在我的演示数据库中有个视图OrdersView,定义代码前面有。 我们来看看,如果直接使用这个视图,会有什么样的执行计划出来:

从这个视图可以看出,SQL Server会对表Orders做全表扫描,应该是很低效的。再来看看下面这个查询:

从这个执行计划可以看出,与上面那个就不一样了。前一个查询中对Orders表的查找是使用【Clustered Index Scan】的方式, 而现在在使用【Clustered Index Seek】的方式了,最右边二个步骤的成本的百分比也发生了改变。这样就足以说明,优化视图时, 最好能根据实际需求,应用不同的过滤条件,再来决定如何去优化。

再来一个由三个查询组成的情况来看看这个视图的执行计划。

select * from dbo.OrdersView where OrderId = 1;
select * from dbo.OrdersView where CustomerId = 1;
select * from dbo.OrdersView where OrderDate >= '2010-12-1' and OrderDate < '2011-12-1';

很明显,对于同一个视图,在不同的过滤条件下,执行计划的差别很明显。

推荐阅读-MSDN文章

索引统计信息
http://msdn.microsoft.com/zh-cn/library/ms190397(SQL.90).aspx

查询优化建议
http://msdn.microsoft.com/zh-cn/library/ms188722(SQL.90).aspx

用于对运行慢的查询进行分析的清单
http://msdn.microsoft.com/zh-cn/library/ms177500(SQL.90).aspx

逻辑运算符和物理运算符引用
http://msdn.microsoft.com/zh-cn/library/ms191158(SQL.90).aspx


猜你喜欢

转载自blog.csdn.net/lzqinfen/article/details/44342021