数据库的查询优化技术

来源:互联网 发布:千牛工作台mac版 编辑:程序博客网 时间:2024/04/29 00:27
数据库系统是管理信息系统的核心,基于数据库的联机事务处理(OLTP)以及联机分析处理(OLAP)是银行、企业、政府等部门最为重要的计算机应用之一。从大多数系统的应用实例来看,查询操作在各种数据库操作中所占据的比重最大,而查询操作所基于的SELECT语句在SQL语句中又是代价最大的语句。举例来说,如果数据的量积累到一定的程度,比如一个银行的账户数据库表信息积累到上百万甚至上千万条记录,全表扫描一次往往需要数十分钟,甚至数小时。如果采用比全表扫描更好的查询策略,往往可以使查询时间降为几分钟,由此可见查询优化技术的重要性。 
笔者在应用项目的实施中发现,许多程序员在利用一些前端数据库开发工具(如PowerBuilder、Delphi等)开发数据库应用程序时,只注重用户界面的华丽,并不重视查询语句的效率问题,导致所开发出来的应用系统效率低下,资源浪费严重。因此,如何设计高效合理的查询语句就显得非常重要。本文以应用实例为基础,结合数据库理论,介绍查询优化技术在现实系统中的运用。 

分析问题 

许多程序员认为查询优化是DBMS(数据库管理系统)的任务,与程序员所编写的SQL语句关系不大,这是错误的。一个好的查询计划往往可以使程序性能提高数十倍。查询计划是用户所提交的SQL语句的集合,查询规划是经过优化处理之后所产生的语句集合。DBMS处理查询计划的过程是这样的:在做完查询语句的词法、语法检查之后,将语句提交给DBMS的查询优化器,优化器做完代数优化和存取路径的优化之后,由预编译模块对语句进行处理并生成查询规划,然后在合适的时间提交给系统处理执行,最后将执行结果返回给用户。在实际的数据库产品(如Oracle、Sybase等)的高版本中都是采用基于代价的优化方法,这种优化能根据从系统字典表所得到的信息来估计不同的查询规划的代价,然后选择一个较优的规划。虽然现在的数据库产品在查询优化方面已经做得越来越好,但由用户提交的SQL语句是系统优化的基础,很难设想一个原本糟糕的查询计划经过系统的优化之后会变得高效,因此用户所写语句的优劣至关重要。系统所做查询优化我们暂不讨论,下面重点说明改善用户查询计划的解决方案。  
解决问题 
下面以关系数据库系统Informix为例,介绍改善用户查询计划的方法。 

1.合理使用索引 
索引是数据库中重要的数据结构,它的根本目的就是为了提高查询效率。现在大多数的数据库产品都采用IBM最先提出的ISAM索引结构。索引的使用要恰到好处,其使用原则如下: 
●在经常进行连接,但是没有指定为外键的列上建立索引,而不经常连接的字段则由优化器自动生成索引。 
●在频繁进行排序或分组(即进行group by或order by操作)的列上建立索引。 
●在条件表达式中经常用到的不同值较多的列上建立检索,在不同值少的列上不要建立索引。比如在雇员表的“性别”列上只有“男”与“女”两个不同值,因此就无必要建立索引。如果建立索引不但不会提高查询效率,反而会严重降低更新速度。 
●如果待排序的列有多个,可以在这些列上建立复合索引(compound index)。 
●使用系统工具。如Informix数据库有一个tbcheck工具,可以在可疑的索引上进行检查。在一些数据库服务器上,索引可能失效或者因为频繁操作而使得读取效率降低,如果一个使用索引的查询不明不白地慢下来,可以试着用tbcheck工具检查索引的完整性,必要时进行修复。另外,当数据库表更新大量数据后,删除并重建索引可以提高查询速度。 

2.避免或简化排序 
应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时,优化器就避免了排序的步骤。以下是一些影响因素: 
●索引中不包括一个或几个待排序的列; 
●group by或order by子句中列的次序与索引的次序不一样; 
●排序的列来自不同的表。 
为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可能影响表的规范化,但相对于效率的提高是值得的)。如果排序不可避免,那么应当试图简化它,如缩小排序的列的范围等。 

3.消除对大型表行数据的顺序存取 
在嵌套查询中,对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存取策略,一个嵌套3层的查询,如果每层都查询1000行,那么这个查询就要查询10亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如,两个表:学生表(学号、姓名、年龄……)和选课表(学号、课程号、成绩)。如果两个表要做连接,就要在“学号”这个连接字段上建立索引。 
还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引,但某些形式的where子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作: 
SELECT * FROM orders WHERE (customer_num=104 AND order_num>1001) OR order_num=1008 
虽然在customer_num和order_num上建有索引,但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合,所以应该改为如下语句: 
SELECT * FROM orders WHERE customer_num=104 AND order_num>1001 
UNION 
SELECT * FROM orders WHERE order_num=1008 
这样就能利用索引路径处理查询。 

4.避免相关子查询 
一个列的标签同时在主查询和where子句中的查询中出现,那么很可能当主查询中的列值改变之后,子查询必须重新查询一次。查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的行。 

5.避免困难的正规表达式 
MATCHES和LIKE关键字支持通配符匹配,技术上叫正规表达式。但这种匹配特别耗费时间。例如:SELECT * FROM customer WHERE zipcode LIKE “98_ _ _” 
即使在zipcode字段上建立了索引,在这种情况下也还是采用顺序扫描的方式。如果把语句改为SELECT * FROM customer WHERE zipcode >“98000”,在执行查询时就会利用索引来查询,显然会大大提高速度。 
另外,还要避免非开始的子串。例如语句:SELECT * FROM customer WHERE zipcode[2,3] >“80”,在where子句中采用了非开始子串,因而这个语句也不会使用索引。

.使用临时表加速查询 
把表的一个子集进行排序并创建临时表,有时能加速查询。它有助于避免多重排序操作,而且在其他方面还能简化优化器的工作。例如: 
SELECT cust.name,rcvbles.balance,……other columns 
FROM cust,rcvbles 
WHERE cust.customer_id = rcvlbes.customer_id 
AND rcvblls.balance>0 
AND cust.postcode>“98000” 
ORDER BY cust.name 
如果这个查询要被执行多次而不止一次,可以把所有未付款的客户找出来放在一个临时文件中,并按客户的名字进行排序: 
SELECT cust.name,rcvbles.balance,……other columns 
FROM cust,rcvbles 
WHERE cust.customer_id = rcvlbes.customer_id 
AND rcvblls.balance>0 
ORDER BY cust.name 
INTO TEMP cust_with_balance 
然后以下面的方式在临时表中查询: 
SELECT * FROM cust_with_balance 
WHERE postcode>“98000” 
临时表中的行要比主表中的行少,而且物理顺序就是所要求的顺序,减少了磁盘I/O,所以查询工作量可以得到大幅减少。 
注意:临时表创建后不会反映主表的修改。在主表中数据频繁修改的情况下,注意不要丢失数据。 

7.用排序来取代非顺序存取 
非顺序磁盘存取是最慢的操作,表现在磁盘存取臂的来回移动。SQL语句隐藏了这一情况,使得我们在写应用程序时很容易写出要求存取大量非顺序页的查询。 
有些时候,用数据库的排序能力来替代非顺序的存取能改进查询。 

实例分析 

下面我们举一个制造公司的例子来说明如何进行查询优化。制造公司数据库中包括3个表,模式如下所示: 
1.part表 
零件号零件描述其他列 
(part_num)(part_desc)(other column) 
102,032Seageat 30G disk…… 
500,049Novel 10M network card…… 
…… 

2.vendor表 
厂商号厂商名其他列 
(vendor _num)(vendor_name) (other column) 
910,257Seageat Corp…… 
523,045IBM Corp…… 
…… 

3.parven表 
零件号厂商号零件数量 
(part_num)(vendor_num)(part_amount) 
102,032910,2573,450,000 
234,423321,0014,000,000 
…… 

下面的查询将在这些表上定期运行,并产生关于所有零件数量的报表: 
SELECT part_desc,vendor_name,part_amount 
FROM part,vendor,parven 
WHERE part.part_num=parven.part_num 
AND parven.vendor_num = vendor.vendor_num 
ORDER BY part.part_num 
如果不建立索引,上述查询代码的开销将十分巨大。为此,我们在零件号和厂商号上建立索引。索引的建立避免了在嵌套中反复扫描。关于表与索引的统计信息如下: 

表行尺寸行数量每页行数量数据页数量 
(table)(row size)(Row count)(Rows/Pages)(Data Pages) 
part15010,00025400 
Vendor1501,000 2540 
Parven13 15,000300 50 
索引键尺寸每页键数量页面数量 
(Indexes)(Key Size)(Keys/Page)(Leaf Pages) 
part450020 
Vendor45002 
Parven825060 

看起来是个相对简单的3表连接,但是其查询开销是很大的。通过查看系统表可以看到,在part_num上和vendor_num上有簇索引,因此索引是按照物理顺序存放的。parven表没有特定的存放次序。这些表的大小说明从缓冲页中非顺序存取的成功率很小。此语句的优化查询规划是:首先从part中顺序读取400页,然后再对parven表非顺序存取1万次,每次2页(一个索引页、一个数据页),总计2万个磁盘页,最后对vendor表非顺序存取1.5万次,合3万个磁盘页。可以看出在这个索引好的连接上花费的磁盘存取为5.04万次。 
实际上,我们可以通过使用临时表分3个步骤来提高查询效率: 

1.从parven表中按vendor_num的次序读数据: 
SELECT part_num,vendor_num,price 
FROM parven 
ORDER BY vendor_num 
INTO temp pv_by_vn 
这个语句顺序读parven(50页),写一个临时表(50页),并排序。假定排序的开销为200页,总共是300页。 

2.把临时表和vendor表连接,把结果输出到一个临时表,并按part_num排序: 
SELECT pv_by_vn,* vendor.vendor_num 
FROM pv_by_vn,vendor 
WHERE pv_by_vn.vendor_num=vendor.vendor_num 
ORDER BY pv_by_vn.part_num 
INTO TMP pvvn_by_pn 
DROP TABLE pv_by_vn 
这个查询读取pv_by_vn(50页),它通过索引存取vendor表1.5万次,但由于按vendor_num次序排列,实际上只是通过索引顺序地读vendor表(40+2=42页),输出的表每页约95行,共160页。写并存取这些页引发5*160=800次的读写,索引共读写892页。 

3.把输出和part连接得到最后的结果: 
SELECT pvvn_by_pn.*,part.part_desc 
FROM pvvn_by_pn,part 
WHERE pvvn_by_pn.part_num=part.part_num 
DROP TABLE pvvn_by_pn 
这样,查询顺序地读pvvn_by_pn(160页),通过索引读part表1.5万次,由于建有索引,所以实际上进行1772次磁盘读写,优化比例为30∶1。笔者在Informix Dynamic Sever上做同样的实验,发现在时间耗费上的优化比例为5∶1(如果增加数据量,比例可能会更大)。 

小结 

20%的代码用去了80%的时间,这是程序设计中的一个著名定律,在数据库应用程序中也同样如此。我们的优化要抓住关键问题,对于数据库应用程序来说,重点在于SQL的执行效率。查询优化的重点环节是使得数据库服务器少从磁盘中读数据以及顺序读页而不是非顺序读页。





如何让你的SQL运行得更快

---- 人们在使用SQL时往往会陷入一个误区,即太关注于所得的结果是否正确,而忽略
了不同的实现方法之间可能存在的性能差异,这种性能差异在大型的或是复杂的数据库
环境中(如联机事务处理OLTP或决策支持系统DSS)中表现得尤为明显。笔者在工作实践
中发现,不良的SQL往往来自于不恰当的索引设计、不充份的连接条件和不可优化的whe
re子句。在对它们进行适当的优化后,其运行速度有了明显地提高!下面我将从这三个
方面分别进行总结:
---- 为了更直观地说明问题,所有实例中的SQL运行时间均经过测试,不超过1秒的均
表示为(< 1秒)。
---- 测试环境--
---- 主机:HP LH II
---- 主频:330MHZ
---- 内存:128兆
---- 操作系统:Operserver5.0.4
----数据库:Sybase11.0.3
一、不合理的索引设计
----例:表record有620000行,试看在不同的索引下,下面几个 SQL的运行情况:
---- 1.在date上建有一非个群集索引
select count(*) from record where date >
''19991201'' and date < ''19991214''and amount >
2000 (25秒)
select date,sum(amount) from record group by date
(55秒)
select count(*) from record where date >
''19990901'' and place in (''BJ'',''SH'') (27秒)
---- 分析:
----date上有大量的重复值,在非群集索引下,数据在物理上随机存放在数据页上,在
范围查找时,必须执行一次表扫描才能找到这一范围内的全部行。
---- 2.在date上的一个群集索引
select count(*) from record where date >
''19991201'' and date < ''19991214'' and amount >
2000 (14秒)
select date,sum(amount) from record group by date
(28秒)
select count(*) from record where date >
''19990901'' and place in (''BJ'',''SH'')(14秒)
---- 分析:
---- 在群集索引下,数据在物理上按顺序在数据页上,重复值也排列在一起,因而在范
围查找时,可以先找到这个范围的起末点,且只在这个范围内扫描数据页,避免了大范
围扫描,提高了查询速度。
---- 3.在place,date,amount上的组合索引
select count(*) from record where date >
''19991201'' and date < ''19991214'' and amount >
2000 (26秒)
select date,sum(amount) from record group by date
(27秒)
select count(*) from record where date >
''19990901'' and place in (''BJ, ''SH'')(< 1秒)
---- 分析:
---- 这是一个不很合理的组合索引,因为它的前导列是place,第一和第二条SQL没有引
用place,因此也没有利用上索引;第三个SQL使用了place,且引用的所有列都包含在组
合索引中,形成了索引覆盖,所以它的速度是非常快的。
---- 4.在date,place,amount上的组合索引
select count(*) from record where date >
''19991201'' and date < ''19991214'' and amount >
2000(< 1秒)
select date,sum(amount) from record group by date
(11秒)
select count(*) from record where date >
''19990901'' and place in (''BJ'',''SH'')(< 1秒)
---- 分析:
---- 这是一个合理的组合索引。它将date作为前导列,使每个SQL都可以利用索引,并
且在第一和第三个SQL中形成了索引覆盖,因而性能达到了最优。
---- 5.总结:
---- 缺省情况下建立的索引是非群集索引,但有时它并不是最佳的;合理的索引设计要
建立在对各种查询的分析和预测上。一般来说:
---- ①.有大量重复值、且经常有范围查询
(between, >,< ,>=,< =)和order by
、group by发生的列,可考虑建立群集索引;
---- ②.经常同时存取多列,且每列都含有重复值可考虑建立组合索引;
---- ③.组合索引要尽量使关键查询形成索引覆盖,其前导列一定是使用最频繁的列。

二、不充份的连接条件:
---- 例:表card有7896行,在card_no上有一个非聚集索引,表account有191122行,在
account_no上有一个非聚集索引,试看在不同的表连接条件下,两个SQL的执行情况:

select sum(a.amount) from account a,
card b where a.card_no = b.card_no(20秒)
---- 将SQL改为:
select sum(a.amount) from account a,
card b where a.card_no = b.card_no and a.
account_no=b.account_no(< 1秒)
---- 分析:
---- 在第一个连接条件下,最佳查询方案是将account作外层表,card作内层表,利用
card上的索引,其I/O次数可由以下公式估算为:
---- 外层表account上的22541页+(外层表account的191122行*内层表card上对应外层
表第一行所要查找的3页)=595907次I/O
---- 在第二个连接条件下,最佳查询方案是将card作外层表,account作内层表,利用
account上的索引,其I/O次数可由以下公式估算为:
---- 外层表card上的1944页+(外层表card的7896行*内层表account上对应外层表每一
行所要查找的4页)= 33528次I/O
---- 可见,只有充份的连接条件,真正的最佳方案才会被执行。
---- 总结:
---- 1.多表操作在被实际执行前,查询优化器会根据连接条件,列出几组可能的连接方
案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的
表;内外表的选择可由公式:外层表中的匹配行数*内层表中每一次查找的次数确定,乘
积最小为最佳方案。
---- 2.查看执行方案的方法-- 用set showplanon,打开showplan选项,就可以看到连
接顺序、使用何种索引的信息;想看更详细的信息,需用sa角色执行dbcc(3604,310,30
2)。
三、不可优化的where子句
---- 1.例:下列SQL条件语句中的列都建有恰当的索引,但执行速度却非常慢:
select * from record where
substring(card_no,1,4)=''5378''(13秒)
select * from record where
amount/30< 1000(11秒)
select * from record where
convert(char(10),date,112)=''19991201''(10秒)
---- 分析:
---- where子句中对列的任何操作结果都是在SQL运行时逐列计算得到的,因此它不得不
进行表搜索,而没有使用该列上面的索引;如果这些结果在查询编译时就能得到,那么
就可以被SQL优化器优化,使用索引,避免表搜索,因此将SQL重写成下面这样:
select * from record where card_no like
''5378%''(< 1秒)
select * from record where amount
< 1000*30(< 1秒)
select * from record where date= ''1999/12/01''
(< 1秒)
---- 你会发现SQL明显快起来!
---- 2.例:表stuff有200000行,id_no上有非群集索引,请看下面这个SQL:
select count(*) from stuff where id_no in(''0'',''1'')
(23秒)
---- 分析:
---- where条件中的''in''在逻辑上相当于''or'',所以语法分析器会将in (''0'',''1'')转化
为id_no =''0'' or id_no=''1''来执行。我们期望它会根据每个or子句分别查找,再将结果
相加,这样可以利用id_no上的索引;但实际上(根据showplan),它却采用了"OR策略"
,即先取出满足每个or子句的行,存入临时数据库的工作表中,再建立唯一索引以去掉
重复行,最后从这个临时表中计算结果。因此,实际过程没有利用id_no上索引,并且完
成时间还要受tempdb数据库性能的影响。
---- 实践证明,表的行数越多,工作表的性能就越差,当stuff有620000行时,执行时
间竟达到220秒!还不如将or子句分开:
select count(*) from stuff where id_no=''0''
select count(*) from stuff where id_no=''1''
---- 得到两个结果,再作一次加法合算。因为每句都使用了索引,执行时间只有3秒,
在620000行下,时间也只有4秒。或者,用更好的方法,写一个简单的存储过程:
create proc count_stuff as
declare @a int
declare @b int
declare @c int
declare @d char(10)
begin
select @a=count(*) from stuff where id_no=''0''
select @b=count(*) from stuff where id_no=''1''
end
select @c=@a+@b
select @d=convert(char(10),@c)
print @d
---- 直接算出结果,执行时间同上面一样快!
---- 总结:
---- 可见,所谓优化即where子句利用了索引,不可优化即发生了表扫描或额外开销。

---- 1.任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时
要尽可能将操作移至等号右边。
---- 2.in、or子句常会使用工作表,使索引失效;如果不产生大量重复值,可以考虑把
子句拆开;拆开的子句中应该包含索引。
---- 3.要善于使用存储过程,它使SQL变得更加灵活和高效。
---- 从以上这些例子可以看出,SQL优化的实质就是在结果正确的前提下,用优化器可
以识别的语句,充份利用索引,减少表扫描的I/O次数,尽量避免表搜索的发生。其实S
QL的性能优化是一个复杂的过程,上述这些只是在应用层次的一种体现,深入研究还会
涉及数据库层的资源配置、网络层的流量控制以及操作系统层的总体设计。

1 逻辑数据库和表的设计 
  数据库的逻辑设计、包括表与表之间的关系是优化关系型数据库性能的核心。一个好的逻辑数据库设计可以为优化数据库和应用程序打下良好的基础。 

  标准化的数据库逻辑设计包括用多的、有相互关系的窄表来代替很多列的长数据表。下面是一些使用标准化表的一些好处。 

A:由于表窄,因此可以使排序和建立索引更为迅速 
B:由于多表,所以多镞的索引成为可能 
C:更窄更紧凑的索引 
D:每个表中可以有少一些的索引,因此可以提高insert update delete等的速度,因为这些操作在索引多的情况下会对系统性能产生很大的影响 
E:更少的空值和更少的多余值,增加了数据库的紧凑性由于标准化,所以会增加了在获取数据时引用表的数目和其间的连接关系的复杂性。太多的表和复杂的连接关系会降低服务器的性能,因此在这两者之间需要综合考虑。 
  定义具有相关关系的主键和外来键时应该注意的事项主要是:用于连接多表的主键和参考的键要有相同的数据类型。 

  2 索引的设计 
A:尽量避免表扫描 
检查你的查询语句的where子句,因为这是优化器重要关注的地方。包含在where里面的每一列(column)都是可能的侯选索引,为能达到最优的性能,考虑在下面给出的例子:对于在where子句中给出了column1这个列。 
下面的两个条件可以提高索引的优化查询性能! 
第一:在表中的column1列上有一个单索引 
第二:在表中有多索引,但是column1是第一个索引的列 
避免定义多索引而column1是第二个或后面的索引,这样的索引不能优化服务器性能 
例如:下面的例子用了pubs数据库。 
SELECT au_id, au_lname, au_fname FROM authors 
WHERE au_lname = ’White’ 
按下面几个列上建立的索引将会是对优化器有用的索引 
?au_lname 
?au_lname, au_fname 
而在下面几个列上建立的索引将不会对优化器起到好的作用 
?au_address 
?au_fname, au_lname 
考虑使用窄的索引在一个或两个列上,窄索引比多索引和复合索引更能有效。用窄的索引,在每一页上 
将会有更多的行和更少的索引级别(相对与多索引和复合索引而言),这将推进系统性能。 
对于多列索引,SQL Server维持一个在所有列的索引上的密度统计(用于联合)和在第一个索引上的 
histogram(柱状图)统计。根据统计结果,如果在复合索引上的第一个索引很少被选择使用,那么优化器对很多查询请求将不会使用索引。 
有用的索引会提高select语句的性能,包括insert,uodate,delete。 
但是,由于改变一个表的内容,将会影响索引。每一个insert,update,delete语句将会使性能下降一些。实验表明,不要在一个单表上用大量的索引,不要在共享的列上(指在多表中用了参考约束)使用重叠的索引。 
在某一列上检查唯一的数据的个数,比较它与表中数据的行数做一个比较。这就是数据的选择性,这比较结果将会帮助你决定是否将某一列作为侯选的索引列,如果需要,建哪一种索引。你可以用下面的查询语句返回某一列的不同值的数目。 
select count(distinct cloumn_name) from table_name 
假设column_name是一个10000行的表,则看column_name返回值来决定是否应该使用,及应该使用什么索引。 
Unique values Index 

5000 Nonclustered index 
20 Clustered index 
3 No index 

镞索引和非镞索引的选择 

<1:>镞索引是行的物理顺序和索引的顺序是一致的。页级,低层等索引的各个级别上都包含实际的数据页。一个表只能是有一个镞索引。由于update,delete语句要求相对多一些的读操作,因此镞索引常常能加速这样的操作。在至少有一个索引的表中,你应该有一个镞索引。 
在下面的几个情况下,你可以考虑用镞索引: 
例如: 某列包括的不同值的个数是有限的(但是不是极少的) 
顾客表的州名列有50个左右的不同州名的缩写值,可以使用镞索引。 
例如: 对返回一定范围内值的列可以使用镞索引,比如用between,>,>=,<,<=等等来对列进行操作的列上。 
select * from sales where ord_date between ’5/1/93’ and ’6/1/93’ 
例如: 对查询时返回大量结果的列可以使用镞索引。 
SELECT * FROM phonebook WHERE last_name = ’Smith’ 

当有大量的行正在被插入表中时,要避免在本表一个自然增长(例如,identity列)的列上建立镞索引。如果你建立了镞的索引,那么insert的性能就会大大降低。因为每一个插入的行必须到表的最后,表的最后一个数据页。 
当一个数据正在被插入(这时这个数据页是被锁定的),所有的其他插入行必须等待直到当前的插入已经结束。 
一个索引的叶级页中包括实际的数据页,并且在硬盘上的数据页的次序是跟镞索引的逻辑次序一样的。 

<2:>一个非镞的索引就是行的物理次序与索引的次序是不同的。一个非镞索引的叶级包含了指向行数据页的指针。 
在一个表中可以有多个非镞索引,你可以在以下几个情况下考虑使用非镞索引。 
在有很多不同值的列上可以考虑使用非镞索引 
例如:一个part_id列在一个part表中 
select * from employee where emp_id = ’pcm9809f’ 
查询语句中用order by 子句的列上可以考虑使用镞索引 

3 查询语句的设计 

SQL Server优化器通过分析查询语句,自动对查询进行优化并决定最有效的执行方案。优化器分析查询语句来决定那个子句可以被优化,并针对可以被优化查询的子句来选择有用的索引。最后优化器比较所有可能的执行方案并选择最有效的一个方案出来。 
在执行一个查询时,用一个where子句来限制必须处理的行数,除非完全需要,否则应该避免在一个表中无限制地读并处理所有的行。 
例如下面的例子, 
select qty from sales where stor_id=7131 
是很有效的比下面这个无限制的查询 
select qty from sales 
避免给客户的最后数据选择返回大量的结果集。允许SQL Server运行满足它目的的函数限制结果集的大小是更有效的。 
这能减少网络I/O并能提高多用户的相关并发时的应用程序性能。因为优化器关注的焦点就是where子句的查询,以利用有用的索引。在表中的每一个索引都可能成为包括在where子句中的侯选索引。为了最好的性能可以遵照下面的用于一个给定列column1的索引。 
第一:在表中的column1列上有一个单索引 
第二:在表中有多索引,但是column1是第一个索引的列不要在where子句中使用没有column1列索引的查询语句,并避免在where子句用一个多索引的非第一个索引的索引。 
这时多索引是没有用的。 
For example, given a multicolumn index on the au_lname, au_fname columns of the authors table in 
the pubs database, 
下面这个query语句利用了au_lname上的索引 
SELECT au_id, au_lname, au_fname FROM authors 
WHERE au_lname = ’White’ 
AND au_fname = ’Johnson’ 
SELECT au_id, au_lname, au_fname FROM authors 
WHERE au_lname = ’White’ 
下面这个查询没有利用索引,因为他使用了多索引的非第一个索引的索引 
SELECT au_id, au_lname, au_fname FROM authors 
WHERE au_fname = ’Johnson’

测试过程:

环境:
PIII900 128M
win2000+sql server2000企业版
table name :t_data
CLUSTERED PRIMARY KEY  :id
Rows:441000

语句1:
select * from t_data where 
id in
(select top 10 id  from t_data where id in
(select top 60000 id  from t_data order by id asc)
order by id desc)
order by id asc

时间:390ms

语句2:
select * from t_data where 
id in
(select top 10 id  from t_data where id in
(select top 100000 id  from t_data order by id asc)
order by id desc)
order by id asc

时间:4s

语句3:
select * from t_data where 
id in
(select top 10 id  from t_data where id in
(select top 150000 id  from t_data order by id asc)
order by id desc)
order by id asc

时间:5s

语句4:
select * from t_data where 
id in
(select top 10 id  from t_data where id in
(select top 200000 id  from t_data order by id asc)
order by id desc)
order by id asc

时间:6s

语句5:
select * from t_data where 
id in
(select top 10 id  from t_data where id in
(select top 250000 id  from t_data order by id asc)
order by id desc)
order by id asc

时间:过了15m没有出来,中断

语句6:
select * from t_data where 
id in
(select top 10 id  from t_data where id in
(select top 300000 id  from t_data order by id asc)
order by id desc)
order by id asc

时间:过了20m没有出来,中断

语句7:
select top 60000 id  into #a from t_data order by id asc

create index ind_a_base_id on #a(id)  

select * from t_data where 
id in (select top 10 id  from #a order by id desc)
order by id asc

时间:20s

语句8:
select top 100000 id  into #a from t_data order by id asc

create index ind_a_base_id on #a(id)  

select * from t_data where 
id in (select top 10 id  from #a order by id desc)
order by id asc

时间:21s

语句9:
select top 150000 id  into #a from t_data order by id asc

create index ind_a_base_id on #a(id)  

select * from t_data where 
id in (select top 10 id  from #a order by id desc)
order by id asc

时间:23s

语句10:
select top 200000 id  into #a from t_data order by id asc

create index ind_a_base_id on #a(id)  

select * from t_data where 
id in (select top 10 id  from #a order by id desc)
order by id asc

时间:24s

语句11:
select top 250000 id  into #a from t_data order by id asc

create index ind_a_base_id on #a(id)  

select * from t_data where 
id in (select top 10 id  from #a order by id desc)
order by id asc

时间:26s

语句12:
select top 300000 id  into #a from t_data order by id asc

create index ind_a_base_id on #a(id)  

select * from t_data where 
id in (select top 10 id  from #a order by id desc)
order by id asc

时间:28s

语句13:
select top 400000 id  into #a from t_data order by id asc

create index ind_a_base_id on #a(id)  

select * from t_data where 
id in (select top 10 id  from #a order by id desc)
order by id asc

时间:29s

语句14:
select * from t_data where 
id in
(select top 10 id  from t_data where id in
(select top 250000 id  from t_data order by id asc)
order by id desc)
order by id asc

时间:过了25m没有出来,还没有中断
 
原创粉丝点击