多列复合索引的使用 绕过微软sql server的一个缺陷

然而,微软sql server在处理这类索引时,有个重要的缺陷,那就是把本该编译成索引seek的操作编成了索引扫描,这可能导致严重性能下降

举个例子来说明问题,假设某个表T有索引 ( cityid, sentdate, userid), 现在有个分页列表功能,要获得大于某个多列复合索引V0的若干个记录的查询,用最简单表意的方式写出来就是 V >= V0, 如果分解开来,就是:
cityid > @cityid0 or (cityid = @cityid0 and (sentdate > @sentdate0 or (sentdate = @sentdate0 and userid >= @userid0))),

当你写出上述查询时,你会期待sql server会自动的把上述识别为V >= V0类型的边界条件,并使用index seek操作来实施该查询。然而,微软的sql server (2005版)有一个重要缺陷(其他的sql server如何还不得知), 当它遇到这样sql时,sql server就会采用index scan来实施,结果是您建立好的索引根本就没有被使用,如果这个表的数据量很大,那所造成的性能下降是非常大的。
对于这个问题,我曾经提交给微软的有关人士,他们进一步要求我去一个正式的网站上去提交这个缺陷,我懒得去做。

不过,对这个缺陷,还是有个办法能够绕过去的,只要把上面给出的条件变变形,sql server还是能够变回到是用index seek, 而不是低性能的index scan. 具体请看我的英文原文吧(对不起了, 我一旦写了中文,就不想翻成英文,反过来也一样, 估计大家英文都还可以,实在不行的就看黑体部分吧, ):
The seek predicate of the form "x > bookmark_of_x" is needed in paging related query. The compiler has no difficulty to parse it correctly if x is a single column index, or two columns index, however, if x is a three columns index or more, then the compiler will have a hard time to recognize it. This failure will result in that the seek predicate ended up in residue predicate, which results in a much worse execution plan.
To illustrate the point, take a example,
Create table A( a int, b int, c int, d float, primary key (a, b, c))
now check the plan for the query:
select c, d from A where (a> 111 or a= 111 and
(b > 222 or b = 222 and c > 333))
you can see a table scan op is used, and the Where clause ended up in residue predicate.
However, if you rewrite the query in an equivalent form:
select c, d from A where a> 111 or a= 111 and b > 222 or a= 111 and b= 222 and c >333
Then the compiler can choose an index seek op, which is desired.
The problem is, the compiler should be able to recognize the first form of seek predicate on multiple columns index, it saves the user from having to pay extra time to figure out a get-around, not to mention the first form is a more efficient form of same expression.
上面的问题,可以说是部分的绕过去了,但是,也有绕不过的时候,接着看下面一段:
It looks like that sql server lacks a consept of vector bookmark, or vector comparison or whatever you like to call it.
The workaround is not a perfect workaround. If sql server were to understand the concept of vector bookmark, then the following two would be the same in execution plan and performance:
1. select top(n) * from A where vectorIndex >= @vectorIndex
2. select * from A where vectorIndex >= @vectorIndex and vectorIndex <=@vectorIndexEnd
-- @vectorIndexEnd corresponds to the last row of 1.
However, test has shown that, the second statement takes far more time than the first statement, and sql server actually only seek to the begining of the vector range and scan to the end of the whole Index, instead of stop at the end of the vector range.
Not only sql server compile badly when the vector bookmark has 3 columns, test has shown that even with as few as 2 columns, sql serer still can not correctly recognize this is actually a vector range, example:
3. select top (100) a, b, c, d from A where a> 60 or a= 60 and b > 20
4. select a, b, c, d from A where (a> 60 or a= 60 and b > 20) and
(a< 60 or a= 60 and b <= 21),

上面两个查询实质相同(表中的数据刚好如此),并且给出同业的结果集,但是,3比4的速度要快的多,如果去看execution plan也证明3确实应当比4快.
也就是说, 即使在索引vectorIndex只含两列的情况下, sql server也无法正确的理解范围表达式 @vectorIndex0 < vectorIndex < @vectorIndex1, 它能把前半部分正确的解读为seek, 但是, 后半部分无法正确解读, 导致, sql server会一直扫描到整个表的末尾, 而不是在@vectorIndex1处停下来.
以下测试代码, 有兴趣的人可以拿去自己玩:

代码如下:

CREATE TABLE [dbo].[A](
[a] [int] NOT NULL,
[b] [int] NOT NULL,
[c] [int] NOT NULL,
[d] [float] NULL,
PRIMARY KEY CLUSTERED ([a] ASC, [b] ASC, [c] ASC)
)
declare @a int, @b int, @c int
set @a =1
while @a <= 100
begin
set @b = 1
begin tran
while @b <= 100
begin
set @c = 1
while @c <= 100
begin
INSERT INTO A (a, b, c, d)
VALUES (@a,@b,@c,@a+@b+@c)
set @c = @c + 1
end
set @b = @b + 1
end
commit
set @a = @a + 1
end
SET STATISTICS PROFILE ON
SET STATISTICS time ON
SET STATISTICS io ON

select top (10) a, b, c, d from A where (a> 60 or a= 60 and
(b > 20 or b = 20 and c >= 31))
select a, b, c, d from A where (a> 60 or a= 60 and
(b > 20 or b = 20 and c >= 31)) and (a< 60 or a= 60 and
(b < 20 or b = 20 and c <= 40))

select top (10) a, b, c, d from A where a> 60 or a= 60 and b > 20 or a= 60 and b= 20 and c >= 31
select a, b, c, d from A where (a> 60 or a= 60 and b > 20 or a= 60 and b= 20 and c >= 31) and
(a< 60 or a= 60 and b < 20 or a= 60 and b= 20 and c <= 40)
select top (100) a, b, c, d from A where a> 60 or a= 60 and b > 20
select a, b, c, d from A where (a> 60 or a= 60 and b > 20) and (a< 60 or a= 60 and b <= 21)
select top (100) a, b, c, d from A where a> 60 or a= 60 and b > 20
select a, b, c, d from A where (a> 60 or a= 60 and b > 20) and (a< 60 or a= 60 and b <= 21)

(0)

相关推荐

  • 多列复合索引的使用 绕过微软sql server的一个缺陷

    然而,微软sql server在处理这类索引时,有个重要的缺陷,那就是把本该编译成索引seek的操作编成了索引扫描,这可能导致严重性能下降 举个例子来说明问题,假设某个表T有索引 ( cityid, sentdate, userid), 现在有个分页列表功能,要获得大于某个多列复合索引V0的若干个记录的查询,用最简单表意的方式写出来就是 V >= V0, 如果分解开来,就是: cityid > @cityid0 or (cityid = @cityid0 and (sentdate >

  • MySQL复合索引的深入探究

    复合索引(又称为联合索引),是在多个列上创建的索引.创建复合索引最重要的是列顺序的选择,这关系到索引能否使用上,或者影响多少个谓词条件能使用上索引.复合索引的使用遵循最左匹配原则,只有索引左边的列匹配到,后面的列才能继续匹配.本文主要探究复合索引的创建顺序与使用情况. (一)复合索引的概念 在单个列上创建的索引我们称为单列索引,在2个以上的列上创建的索引称为复合索引.在单个列上创建索引相对简单,通常只需要考虑列的选择率即可,选择性越好,代表数据越分散,创建出来的索引性能也就更好.通常,某列选择率

  • SQL Server 数据库索引其索引的小技巧

    一.什么是索引 减少磁盘I/O和逻辑读次数的最佳方法之一就是使用[索引] 索引允许SQL Server在表中查找数据而不需要扫描整个表. 1.1.索引的好处: 当表没有聚集索引时,成为[堆或堆表] [堆]是一堆未加工的数据,以行标识符作为指向存储位置的指针.表数据没有顺序,也不能搜索,除非逐行遍历.这个过程称为[扫描].当存在聚集索引时,非聚集索引的指针由聚集索引所定义的值组成,所以聚集索引变得非常重要. 因为页面大小固定,所以列越少,所能存储的行就越多.由于非聚集索引通常不包含所有列,所以一般

  • SQL Server数据库中伪列及伪列的含义详解

    SQL Server中的伪列 下午看QQ群有人在讨论(非聚集)索引的存储,说,对于聚集索引表,非聚集索引存储的是索引键值+聚集索引键值:对于非聚集索引表,索引存储的是索引键值+RowId,这应该是一个常识,对此不作具体详细阐述. 这里主要是提到的RowId引起了一点思考. 那么,这个RowId是个什么玩意?能不能更加直观一点来看看RowId的信息?代表什么含义?这个当然也是可以的. Oracle中的表中有一个伪列的概念,就是在查询表的时候加上select rowid,* from Table,会

  • Sql Server中判断表、列不存在则创建的方法

    一.Sql Server中如何判断表中某列是否存在 首先跟大家分享Sql Server中判断表中某列是否存在的两个方法,方法示例如下: 比如说要判断表A中的字段C是否存在两个方法: 第一种方法  IF EXISTS ( SELECT 1 FROM SYSOBJECTS T1 INNER JOIN SYSCOLUMNS T2 ON T1.ID=T2.ID WHERE T1.NAME='A' AND T2.NAME='C' ) PRINT '存在' ELSE PRINT '不存在' 第二种方法,短小

  • SQL Server 索引结构及其使用(二) 改善SQL语句第1/3页

    比如: select * from table1 where name=''zhangsan'' and tID > 10000 和执行: select * from table1 where tID > 10000 and name=''zhangsan'' 一些人不知道以上两条语句的执行效率是否一样,因为如果简单的从语句先后上看,这两个语句的确是不一样,如果tID是一个聚合索引,那么后一句仅仅从表的10000条以后的记录中查找就行了:而前一句则要先从全表中查找看有几个name=''zhan

  • Oracle复合索引与空值的索引使用问题小结

    昨天在QQ群里讨论一个SQL优化的问题,语句大致如下: select A,min(B) from table group by A;--A,B都没有not null约束,A列无空值,B列有空值.--存在复合索引IX_TEST(A,B) 于是手动测试,环境采用Oracle自带的scott用户下的emp表. 1.首先查看如下语句的执行计划(此时表只有主键索引): 2.添加IX_TEST(deptno,comm)后查看执行计划: 发现依然是全表扫描. 3.为deptno列添加非空约束后再次查看执行计划

  • mysql的联合索引(复合索引)的实现

    联合索引 本文中联合索引的定义为(MySQL): ALTER TABLE `table_name` ADD INDEX (`col1`,`col2`,`col3`); 联合索引的优点 若多个一条SQL,需要多个用到两个条件 SELECT * FROM `user_info` WHERE username='XX',password='XXXXXX'; 当索引在检索 password字段的时候,数据量大大缩小,索引的命中率减小,增大了索引的效率. 符合索引的索引体积比单独索引的体积要小,而且只是一

  • MySQL的复合索引总结

    目录 1.背景 2.认识复合索引 3.最左匹配原则 4.字段顺序的影响 5.单字段是否可以触发索引? 7.复合索引可以替代单一索引吗? 1.背景 最近频繁出现慢SQL导致系统性能问题,于是决定针对索引进行一些优化.一些表结构本身已经有了不少索引,如果再继续添加索引,势必会影响到插入数据的性能.那么,是否可以使用组合索引来达到目的呢?这篇文章咱们来一探究竟. 2.认识复合索引 如果where条件中使用到多个字段,并且需要对多个字段建立索引,此时就可以考虑采用复合索引(组合索引).比如查询地址时需要

  • Mysql的复合索引如何生效

    目录 背景 认识复合索引 最左匹配原则 字段顺序的影响 复合索引可以替代单一索引吗? 小结 背景 最近频繁出现慢SQL导致系统性能问题,于是决定针对索引进行一些优化.一些表结构本身已经有了不少索引,如果再继续添加索引,势必会影响到插入数据的性能.那么,是否可以使用组合索引来达到目的呢?这篇文章咱们来一探究竟. 认识复合索引 如果where条件中使用到多个字段,并且需要对多个字段建立索引,此时就可以考虑采用复合索引(组合索引).比如查询地址时需要输入省.市,那么在省.市上建立索引,当数据量大时会明

随机推荐