SQLServer在利用索引进行查找之前,会先判断索引包含的字段是否适用于查询条件中的谓词。
单列索引
单列索引的情况比较简单。SQL Server能够利用单列索引完成包括等值和非等值(大于、小于等)在内的大多数比较。而诸如在字段上使用了函数的或在起始位置使用了通配符的“like”谓词的情况,一般会导致SQLServer不能利用索引进行查找。
例如,假设现在有一个在字段“a”上面单列索引。以下的谓词可以利用索引进行查找:
- a = 3.14
- a > 100
- a between 0 and 99
- a like ‘abc%’
- a in (2, 3, 5, 7)
但是,以下的谓词不能利用索引:
- ABS(a) = 1
- a + 1 = 9
- a like ‘%abc’
多列索引
多列索引稍微有一点复杂。对于多列索引,其键的排序很重要。键的排序决定了索引的排序并且对于SQL Server使用索引处理查询谓词有影响。
让我们来用电话本来更形象的解释为什么字段排序会这么重要。电话本就如同包含了(名字、姓氏)的索引。电话本的内容按照名字排列,所以当你知道了某个人的名字,很容易就能查到他的电话。但是,如果我们只知道一个姓氏,查到这个姓氏的所有人将会很难。这时我们就需要一个按照姓氏排序的电话本。
同样的,如果我们的索引包含两个字段,那么我们只能在第一个字段上存在等值谓词时在第二个字段上使用索引来查找。即使我们无法将索引用于第二个字段,我们仍可以将它用于第一个字段。这种情况则需要对索引的第二个字段使用驻留谓词。这里的驻留谓词和我们用于扫描的那个是相同的。
例如,假设现在有一个定义在列“a”和列“b”上面的索引。我们可以将这个索引用于任何可以利用单列索引查找的谓词。在附加了这些谓词后我们同样可以使用它:
- a = 3.14 and b = ‘pi’
- a = ‘xyzzy’ and b <= 0
对于下一组例子,我们只能将索引用于字段a,而不能用在字段b上。对于这些情况,我们需要使用驻留谓词。
- a > 100 and b > 100
- a like ‘abc%’ and b = 2
对于最后这组例子,我们不能使用索引来查找,甚至连在字段a上面查找都不可以。在这些情况下,我们必须使用不同的索引(例如一个字段b排在首位索引)或者我们必须通过一个驻留谓词来扫描。
- b = 0
- a + 1 = 9 and b between 1 and 9
- a like ‘%abc’ and b in (1, 3, 5)
一个稍微具体些例子
看一下下面的情况:
create table person (id int,last_name varchar(30), first_name varchar(30))
create unique clustered index person_id on person (id)
create index person_name on person(last_name, first_name)
以下是三个查询和他们的相应的文字描述的查询计划。第一个查询对于两个字段的查找都用到了person_name索引。第二个查询只将索引用于查找第一个字段,并且在frist_name字段上面使用了驻留谓词。第三个查询不能使用查找的方式执行,它通过驻留谓词进行了扫描。
select id from person where last_name = 'Doe' and first_name = 'John'
|--Index Seek(OBJECT:([person].[person_name]), SEEK:([person].[last_name]='Doe'AND [person].[first_name]='John'))
select id from person where last_name > 'Doe'and first_name = 'John'
|--Index Seek(OBJECT:([person].[person_name]),SEEK:([person].[last_name] > 'Doe'), WHERE:([person].[first_name]='John'))
select id from person where last_name like '%oe' and first_name = 'John'
|--Index Scan(OBJECT:([person].[person_name]), WHERE:([person].[first_name]='John' AND [person].[last_name] like '%oe'))
注意:考虑到大家会试着重新执行下这些例子,在这个和之前的一些例子当中,我使用了索引提示(并没有展示出来)来确保得到我想要的展示结果,而并没有向表中插入数据。
一点关于索引键的知识
大多数情况,索引的键就是那组你在创建索引时声明的字段。然而,当你在一个拥有聚集索引的表上创建一个非唯一且非聚集索引时,即使聚集索引的键没有显式声明为非聚集索引的键的一部分,默认会把聚集索引的键加到非聚集索引的键里面。你可以在这些隐藏的键上面查找就好像你显式声明了他们一样。
例如,下面的例子:
create table T_heap (a int, b int, c int, d int, e int, f int)
create index T_heap_a on T_heap (a)
create index T_heap_bc on T_heap(b, c)
create index T_heap_d on T_heap (d) include (e)
create unique index T_heap_f on T_heap(f)
create table T_clu (a int, b int, c int, d int, e int, f int)
create unique clustered index T_clu_a on T_clu (a)
create index T_clu_b on T_clu (b)
create index T_clu_ac on T_clu (a, c)
create index T_clu_d on T_clu (d) include (e)
create unique index T_clu_f on T_clu(f)
每个索引的键字段和他所包含的字段如下:
索引 |
键字段 |
包括的字段 |
T_heap_a |
a |
a |
T_heap_bc |
b, c |
b, c |
T_heap_d |
d |
d, e |
T_heap_f |
f |
f |
T_clu_a |
a |
a, b, c, d, e |
T_clu_b |
b, a |
a, b |
T_clu_ac |
a, c |
a, c |
T_clu_d |
d, a |
a, d, e |
T_clu_f |
f |
a, f |
注意,表T_clu上的每一个非聚集索引都包含了聚集索引的键字段a,包括唯一索引T_clu_f。
下次接着写一写…
我计划再写一篇关于扫描和查找的文章。我将写一些SQL Server在决定用哪个索引时、决定采取扫描、查找还是用书签查找时所做的权衡。
原文链接:http://blogs.msdn.com/b/craigfr/archive/2006/07/07/652668.aspx