大型系统的生产环境,一般情况下,我们评价一条查询是否有效率,更多的是关注逻辑 IO( 至于为什么,回头补一篇 ) 。我们常说,“要建彪悍的索引”、“要写高效的 SQL ”,其实最终目的就是在相同结果集情况下,尽可能减少逻辑 IO 。
没统计信息 SQLServer 就无法估算不同查询计划开销优劣,而只能采用最稳妥的 Scan (不管是 table scan 还是 clustered index scan )。一般情况下我们不会犯这种错误—— where 条件里不使用非索引列是个常识。索引上的统计信息是无法删除的。
这条规则被广为传颂,原因据联机文档和百敬同学的书讲,也是 SQLServer 无法评估不同查询计划开销的优劣。但是 SqlServer2k5 聪明了很多,试验发现尽管用了 != 或者 not ,查询还是会被优化。如下:
create table tb1
(
col1 int identity ( 1, 1) primary key ,
col2 int not null,
col3 varchar ( 64) not null
)
create index ix_tb1_col2 on tb1
(
col2
)
create index ix_tb1_col3 on tb1
(
col3
)
declare @f int
set @f = 0
while @f < 9999
begin
insert into tb1 ( col2, col3) values ( 1, 'ssdd' )
set @f = @f + 1
end
insert into tb1 ( col2, col3) values ( 0, 'aadddd' )
insert into tb1 ( col2, col3) values ( 2, 'bbddd' )
insert into tb1 ( col2, col3) values ( 3, 'bbaaddddddaa' )
通过上述代码,各位可以看到数据分布。 col2 值为 1 的有 9999 条; col2 值为 0 、 2 、 3 的分别有 1 条。
按照本条规则, != 和 NOT 带来的应该是个 scan 操作,但实际情况是:
SQL2k5 很聪明,它依据统计信息分析得出来,应该采用 index seek 而不是 index scan 。(稍微解释解释 index seek 和 index scan :索引是一颗 B 树, index seek 是查找从 B 树的根节点开始,一级一级找到目标行。 index scan 则是从左到右,把整个 B 树遍历一遍。假设唯一的目标行位于索引树(假设是非聚集索引,树深度 2 ,叶节点占用 k 页物理存储)最右的叶节点上(如上例)。 index seek 引起的 IO 是 4 ,而 index scan 引起的 IO 是 K ,性能差别巨大。关于索引,可以仔细读读联机文档关于物理数据库体系结构部分 )。
这些运算包括字符串连接(如: select * from Users where UserName + ‘pig’ = ‘ 张三 pig’ ),通配符在前面的 Like 运算(如: select * from tb1 where col4 like ‘%aa’ ),使用其他用户自定义函数、系统内置函数、标量函数等等(如: select * from UserLog where datepart(dd, LogTime) = 3 )。
SQLServer 在处理以上语句时,一样没办法估算开销。最终结果当然是 clustered index scan 或者 table scan 了。
所谓的“运算”包括加减乘除或通过一些 function (如: select * from tb where col1 – col2 = 1997 ),也包括比较运算(如: select * from tb where col1 > col2 )。这种情况下, SQLServer 一样没办法估算开销。不论 col1 、 col2 上都有索引还是创建了 col1 、 col2 上的覆盖索引还是创建了 col1 include col2 的索引。
但是这种查询有解决办法,可以在表上多创建一个计算字段,其值设置为你的“运算”结果,再在该字段上创建一个索引,就 Ok 了。
原创地址:
http://www.cnblogs.com/cn_wpf/archive/2007/08/03/841911.html