快捷搜索:

SQL Server2000 索引结构及其使用 - 索引结构

一、深入浅出理解索引布局

实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER供给了两种索引:凑集索引(clustered index,也称聚类索引、簇集索引)和非凑集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来阐明一下凑集索引和非凑集索引的差别:

着实,我们的汉语字典的正文本身便是一个凑集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,由于“安”的拼音是“an”,而按照拼音排序汉字的字典因此英翰墨母“a”开首并以“z”结尾的,那么“安”字就自然地排在字典的前部。假如您翻完了所有以“a”开首的部分仍旧找不到这个字,那么就阐明您的字典中没有这个字;同样的,假如查“张”字,那您也会将您的字典翻到着末部分,由于“张”的拼音是“zhang”。也便是说,字典的正文部分本身便是一个目录,您不必要再去查其他目录来找到您必要找的内容。我们把这种正文内容本身便是一种按照必然规则排列的目录称为“凑集索引”。

假如您熟识某个字,您可以快速地从自动中查到这个字。但您也可能会碰到您不熟识的字,不知道它的发音,这时刻,您就不能按照刚才的措施找到您要查的字,而必要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序措施,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分手位于“张”字的高低方,现在您看到的继续的“驰、张、弩”三字实际上便是他们在非凑集索引中的排序,是字典正文中的字在非凑集索引中的映射。我们可以经由过程这种要领来找到您所必要的字,但它必要两个历程,先找到目录中的结果,然后再翻到您所必要的页码。我们把这种目录纯挚是目录,正文纯挚是正文的排序要领称为“非凑集索引”。

经由过程以上例子,我们可以理解到什么是“凑集索引”和“非凑集索引”。进一步引申一下,我们可以很轻易的理解:每个表只能有一个凑集索引,由于目录只能按照一种措施进行排序。

二、何时应用凑集索引或非凑集索引

下面的表总结了何时应用凑集索引或非凑集索引(很紧张):

动作描述

应用凑集索引

应用非凑集索引

列常常被分组排序

返回某范围内的数据

不应

一个或极少不合值

不应

不应

小数目的不合值

不应

大年夜数目的不合值

不应

频繁更新的列

不应

外键列

主键列

频繁改动索引列

不应

事实上,我们可以经由过程前面凑集索引和非凑集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个光阴列,正好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的整个数据时,这个速率就将是很快的,由于您的这本字典正文是按日期进行排序的,聚类索引只必要找到要检索的所稀有据中的开首和结尾数据即可;而不像非凑集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到详细内容。

三、结合实际,谈索引应用的误区

理论的目的是利用。虽然我们刚才列出了何时应应用凑集索引或非凑集索引,但在实践中以上规则却很轻易被漠视或不能根据实际环境进行综合阐发。下面我们将根据在实践中碰到的实际问题来谈一下索引应用的误区,以便于大年夜家掌握索引建立的措施。

1、主键便是凑集索引

这种设法主见笔者觉得是极度差错的,是对凑集索引的一种挥霍。虽然SQL SERVER默认是在主键上建立凑集索引的。

平日,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大年夜的,步长一样平常为1。我们的这个办公自动化的实例中的列Gid便是如斯。此时,假如我们将这个列设为主键,SQL SERVER会将此列默觉得凑集索引。这样做有好处,便是可以让您的数据在数据库中按照ID进行物理排序,但笔者觉得这样做意义不大年夜。

显而易见,凑集索引的上风是很显着的,而每个表中只能有一个凑集索引的规则,这使得凑集索引变得加倍贵重。

从我们前面谈到的凑集索引的定义我们可以看出,应用凑集索引的最大年夜好处便是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际利用中,由于ID号是自动天生的,我们并不知道每笔记录的ID号,以是我们很难在实践顶用ID号来进行查询。这就使让ID号这个主键作为凑集索引成为一种资本挥霍。其次,让每个ID号都不合的字段作为凑集索引也不相符“大年夜数目的不合值环境下不应建立聚合索引”规则;当然,这种环境只是针对用户常常改动记录内容,分外是索引项的时刻会负感化,但对付查询速率并没有影响。

在办公自动化系统中,无论是系统首页显示的必要用户签收的文件、会议照样用户进行文件查询等任何环境下进行数据查询都离不开字段的是“日期”还有用户本身的“用户名”。

平日,办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限定当前用户尚未签收的环境,但假如您的系统已建立了很长光阴,并且数据量很大年夜,那么,每次每个用户打开头页的时刻都进行一次全表扫描,这样做意义是不大年夜的,绝大年夜多半的用户1个月前的文件都已经浏览过了,这样做只能徒增数据库的开销而已。事实上,我们完全可以让用户打开系统首页时,数据库仅仅查询这个用户近3个月来未阅览的文件,经由过程“日期”这个字段来限定表扫描,前进查询速率。假如您的办公自动化系统已经建立的2年,那么您的首页显示速率理论上将是原本速率8倍,以致更快。

在这里之以是提到“理论上”三字,是由于假如您的凑集索引照样盲目地建在ID这个主键上时,您的查询速率是没有这么高的,纵然您在“日期”这个字段上建立的索引(非聚合索引)。下面我们就来看一下在1000万条数据量的环境下各类查询的速率体现(3个月内的数据为25万条):

(1)仅在主键上建立凑集索引,并且不划分光阴段:

Select gid,fariqi,neibuyonghu,title from tgongwen

用时:128470毫秒(即:128秒)

(2)在主键上建立凑集索引,在fariq上建立非凑集索引:

select gid,fariqi,neibuyonghu,title from Tgongwen

where fariqi> dateadd(day,-90,getdate())

用时:53763毫秒(54秒)

(3)将聚合索引建立在日期列(fariqi)上:

select gid,fariqi,neibuyonghu,title from Tgongwen

where fariqi> dateadd(day,-90,getdate())

用时:2423毫秒(2秒)

虽然每条语句提掏出来的都是25万条数据,各类环境的差异却是伟大年夜的,分外是将凑集索引建立在日期列时的差异。事实上,假如您的数据库真的有1000万容量的话,把主键建立在ID列上,就像以上的第1、2种环境,在网页上的体现便是超时,根本就无法显示。这也是我摒弃ID列作为凑集索引的一个最紧张的身分。得出以上速率的措施是:在各个select语句前加:

declare @d datetime

set @d=getdate()

并在select语句后加:

select [语句履行花费光阴(毫秒)]=datediff(ms,@d,getdate())

2、只要建立索引就能显明前进查询速率

事实上,我们可以发明上面的例子中,第2、3条语句完全相同,且建立索引的字段也相同;不合的仅是前者在fariqi字段上建立的长短聚合索引,后者在此字段上建立的是聚合索引,但查询速率却有着天地之别。以是,并非是在任何字段上简单地建立索引就能前进查询速率。

从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不合记录。在此字段上建立聚合索引是再相宜不过了。在现实中,我们天天都邑发几个文件,这几个文件的发文日期就相同,这完全相符建立凑集索引要求的:“既不能绝大年夜多半都相同,又不能只有极少数相同”的规则。由此看来,我们建立“适当”的聚合索引对付我们前进查询速率是异常紧张的。

3、把所有必要前进查询速率的字段都加进凑集索引,以前进查询速率

上面已经谈到:在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是如斯的紧张,我们可以把他们合并起来,建立一个复合索引(compound index)。

很多人觉得只要把任何字段加进凑集索引,就能前进查询速率,也有人认为迷惑:假如把复合的凑集索引字段分开查询,那么查询速率会减慢吗?带着这个问题,我们来看一下以下的查询速率(结果集都是25万条数据):(日期列fariqi首先排在复合凑集索引的肇端列,用户名neibuyonghu排在后列):

(1)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>''2004-5-5''

查询速率:2513毫秒

(2)select gid,fariqi,neibuyonghu,title from Tgongwen

where fariqi>''2004-5-5'' and neibuyonghu=''办公室''

查询速率:2516毫秒

(3)select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu=''办公室''

查询速率:60280毫秒

从以上试验中,我们可以看到假如仅用凑集索引的肇端列作为查询前提和同时用到复合凑集索引的整个列的查询速率是险些一样的,以致比用上整个的复合索引列还要略快(在查询结果集数目一样的环境下);而假如仅用复合凑集索引的非肇端列作为查询前提的话,这个索引是不起任何感化的。当然,语句1、2的查询速率一样是由于查询的条款数一样,假如复合索引的所有列都用上,而且查询结果少的话,这样就会形成“索引覆盖”,因而机能可以达到最优。同时,请记着:无论您是否常常应用聚合索引的其他列,但其前导列必然如果应用最频繁的列。

四、其他书上没有的索引应用履历总结

1、用聚合索引比用不是聚合索引的主键速率快

下面是实例语句:(都是提取25万条数据)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=''2004-9-16''

应用光阴:3326毫秒

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid

应用光阴:4470毫秒

这里,用聚合索引比用不是聚合索引的主键速率快了近1/4。

2、用聚合索引比用一样平常的主键作order by时速率快,分外是在小数据量环境下

select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi

用时:12936

select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid

用时:18843

这里,用聚合索引比用一样平常的主键作order by时,速率快了3/10。事实上,假如数据量很小的话,用凑集索引作为排序列要比应用非凑集索引速率快得显着的多;而数据量假如很大年夜的话,如10万以上,则二者的速率区别不显着。

3、应用聚合索引内的光阴段,搜索光阴会按数据占全部数据表的百分比成比例削减,而无论聚合索引应用了若干个:

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>''2004-1-1''

用时:6343毫秒(提取100万条)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>''2004-6-6''

用时:3170毫秒(提取50万条)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=''2004-9-16''

用时:3326毫秒(和上句的结果如出一辙。假如采集的数量一样,那么用大年夜于号和即是号是一样的)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen

where fariqi>''2004-1-1'' and fariqi

用时:3280毫秒

4、日期列不会由于有分秒的输入而减慢查询速率

下面的例子中,共有100万条数据,2004年1月1日今后的数据有50万条,但只有两个不合的日期,日期正确到日;之前稀有据50万条,有5000个不合的日期,日期正确到秒。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen

where fariqi>''2004-1-1'' order by fariqi

用时:6390毫秒

select gid,fariqi,neibuyonghu,reader,title from Tgongwen

where fariqi

用时:6453毫秒

五、其他留意事变

“水可载舟,亦可覆舟”,索引也一样。索引有助于前进检索机能,但过多或欠妥的索引也会导致系统低效。由于用户在表中每加进一个索引,数据库就要做更多的事情。过多的索引以致会导致索引碎片。

以是说,我们要建立一个“适当”的索引体系,分外是对聚合索引的创建,更应千锤百炼,以使您的数据库能获得高机能的发挥。

当然,在实践中,作为一个尽职的数据库治理员,您还要多测试一些规划,找出哪种规划效率最高、最为有效。

改良SQL语句

很多人不知道SQL语句在SQL SERVER中是若何履行的,他们担心自己所写的SQL语句会被SQL SERVER误解。比如:

select * from table1 where name=''zhangsan'' and tID > 10000

和履行:

select * from table1 where tID > 10000 and name=''zhangsan''

一些人不知道以上两条语句的履行效率是否一样,由于假如简单的从语句先后上看,这两个语句切实着实是不一样,假如tID是一个聚合索引,那么后一句仅仅从表的10000条今后的记录中查找就行了;而前一句则要先从全表中查找看有几个name=''zhangsan''的,而后再根据限定前提前提tID>10000来提出查询结果。

事实上,这样的担心是不需要的。SQL SERVER中有一个“查询阐发优化器”,它可以谋略出where子句中的搜索前提并确定哪个索引能缩小表扫描的搜索空间,也便是说,它能实现自动优化。

虽然查询优化器可以根据where子句自动的进行查询优化,但大年夜家仍旧有需要懂得一下“查询优化器”的事情道理,如非这样,无意偶尔查询优化器就会不按照您的本意进行快速查询。

在查询阐发阶段,查询优化器查看查询的每个阶段并抉择限定必要扫描的数据量是否有用。假如一个阶段可以被用作一个扫描参数(SARG),那么就称之为可优化的,并且可以使用索引快速得到所需数据。

SARG的定义:用于限定搜索的一个操作,由于它平日是指一个特定的匹配,一个值得范围内的匹配或者两个以上前提的AND连接。形式如下:

列名 操作符

操作符列名

列名可以呈现在操作符的一边,而常数或变量呈现在操作符的另一边。如:

Name=’张三’

价格>5000

50005000

假如一个表达式不能满意SARG的形式,那它就无法限定搜索的范围了,也便是SQL SERVER必须对每一行都判断它是否满意WHERE子句中的所有前提。以是一个索引对付不满意SARG形式的表达式来说是无用的。

先容完SARG后,我们来总结一下应用SARG以及在实践中碰到的和某些资料上结论不合的履历:

1、Like语句是否属于SARG取决于所应用的通配符的类型

如:name like ‘张%’ ,这就属于SARG

而:name like ‘%张’ ,就不属于SARG。

缘故原由是通配符%在字符串的开通使得索引无法应用。

2、or 会引起全表扫描

Name=’张三’ and 价格>5000 符号SARG,而:Name=’张三’ or 价格>5000 则不相符SARG。应用or会引起全表扫描。

3、非操作符、函数引起的不满意SARG形式的语句

不满意SARG形式的语句最范例的环境便是包括非操作符的语句,如:NOT、!=、、NOT EXISTS、NOT IN、NOT LIKE等,别的还有函数。下面便是几个不满意SARG形式的例子:

ABS(价格)5000

SQL SERVER也会觉得是SARG,SQL SERVER会将此式转化为:

WHERE 价格>2500/2

但我们不保举这样应用,由于无意偶尔SQL SERVER不能包管这种转化与原始表达式是完全等价的。

4、IN 的感化相称与OR

语句:

Select * from table1 where tid in (2,3)

Select * from table1 where tid=2 or tid=3

是一样的,都邑引起全表扫描,假如tid上有索引,其索引也会掉效。

5、只管即便少用NOT

6、exists 和 in 的履行效率是一样的

很多资料上都显示说,exists要比in的履行效率要高,同时应尽可能的用not exists来代替not in。但事实上,我试验了一下,发明二者无论是前面带不带not,二者之间的履行效率都是一样的。由于涉及子查询,我们试验此次用SQL SERVER自带的pubs数据库。运行前我们可以把SQL SERVER的statistics I/O状态打开:

(1)select title,price from titles where title_id in (select title_id from sales where qty>30)

该句的履行结果为:

表 ''sales''。扫描计数 18,逻辑读 56 次,物理读 0 次,预读 0 次。

表 ''titles''。扫描计数 1,逻辑读 2 次,物理读 0 次,预读 0 次。

(2)select title,price from titles

where exists (select * from sales

where sales.title_id=titles.title_id and qty>30)

第二句的履行结果为:

表 ''sales''。扫描计数 18,逻辑读 56 次,物理读 0 次,预读 0 次。

表 ''titles''。扫描计数 1,逻辑读 2 次,物理读 0 次,预读 0 次。

我们从此可以看到用exists和用in的履行效率是一样的。

7、用函数charindex()和前面加通配符%的LIKE履行效率一样

前面,我们谈到,假如在LIKE前面加上通配符%,那么将会引起全表扫描,以是其履行效率是低下的。但有的资料先容说,用函数charindex()来代替LIKE速率会有大年夜的提升,经我试验,发明这种阐明也是差错的:

select gid,title,fariqi,reader from tgongwen

where charindex(''刑侦支队'',reader)>0 and fariqi>''2004-5-5''

用时:7秒,别的:扫描计数 4,逻辑读 7155 次,物理读 0 次,预读 0 次。

select gid,title,fariqi,reader from tgongwen

where reader like ''%'' + ''刑侦支队'' + ''%'' and fariqi>''2004-5-5''

用时:7秒,别的:扫描计数 4,逻辑读 7155 次,物理读 0 次,预读 0 次。

8、union并一向比较or的履行效率高

我们前面已经谈到了在where子句中应用or会引起全表扫描,一样平常的,我所见过的资料都是保举这里用union来代替or。事实证实,这种说法对付大年夜部分都是适用的。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen

where fariqi=''2004-9-16'' or gid>9990000

用时:68秒。扫描计数 1,逻辑读 404008 次,物理读 283 次,预读 392163 次。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=''2004-9-16''

union

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid>9990000

用时:9秒。扫描计数 8,逻辑读 67489 次,物理读 216 次,预读 7499 次。

看来,用union在平日环境下比用or的效率要高的多。

但颠末试验,笔者发明假如or两边的查询列是一样的话,那么用union则反倒和用or的履行速率差很多,虽然这里union扫描的是索引,而or扫描的是全表。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen

where fariqi=''2004-9-16'' or fariqi=''2004-2-5''

用时:6423毫秒。扫描计数 2,逻辑读 14726 次,物理读 1 次,预读 7176 次。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=''2004-9-16''

union

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=''2004-2-5''

用时:11640毫秒。扫描计数 8,逻辑读 14806 次,物理读 108 次,预读 1144 次。

9、字段提取要按照“需若干、提若干”的原则,避免“select *”

我们来做一个试验:

select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc

用时:4673毫秒

select top 10000 gid,fariqi,title from tgongwen order by gid desc

用时:1376毫秒

select top 10000 gid,fariqi from tgongwen order by gid desc

用时:80毫秒

由此看来,我们每少提取一个字段,数据的提取速率就会有响应的提升。提升的速率还要看您舍弃的字段的大年夜小来判断。

10、count(*)不比count(字段)慢

某些资料上说:用*会统计所有列,显然要比一个天下的列名效率低。这种说法着实是没有根据的。我们来看:

select count(*) from Tgongwen

用时:1500毫秒

select count(gid) from Tgongwen

用时:1483毫秒

select count(fariqi) from Tgongwen

用时:3140毫秒

select count(title) from Tgongwen

用时:52050毫秒

从以上可以看出,假如用count(*)和用count(主键)的速率是相称的,而count(*)却比其他任何除主键以外的字段汇总速率要快,而且字段越长,汇总的速率就越慢。我想,假如用count(*), SQL SERVER可能会自动查找最小字段来汇总的。当然,假如您直接写count(主键)将会来的更直接些。

11、order by按凑集索引列排序效率最高

我们来看:(gid是主键,fariqi是聚合索引列):

select top 10000 gid,fariqi,reader,title from tgongwen

用时:196 毫秒。 扫描计数 1,逻辑读 289 次,物理读 1 次,预读 1527 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc

用时:4720毫秒。 扫描计数 1,逻辑读 41956 次,物理读 0 次,预读 1287 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc

用时:4736毫秒。 扫描计数 1,逻辑读 55350 次,物理读 10 次,预读 775 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc

用时:173毫秒。 扫描计数 1,逻辑读 290 次,物理读 0 次,预读 0 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc

用时:156毫秒。 扫描计数 1,逻辑读 289 次,物理读 0 次,预读 0 次。

从以上我们可以看出,不排序的速率以及逻辑读次数都是和“order by 凑集索引列” 的速率是相称的,但这些都比“order by 非凑集索引列”的查询速率是快得多的。

同时,按照某个字段进行排序的时刻,无论是正序照样倒序,速率是基真相当的。

12、高效的TOP

事实上,在查询和提取超大年夜容量的数据集时,影响数据库相应光阴的最大年夜身分不是数据查找,而是物理的I/0操作。如:

select top 10 * from (

select top 10000 gid,fariqi,title from tgongwen

where neibuyonghu=''办公室''

order by gid desc) as a

order by gid asc

这条语句,从理论上讲,整条语句的履行光阴应该比子句的履行光阴长,但事实相反。由于,子句履行后返回的是10000笔记录,而整条语句仅返回10条语句,以是影响数据库相应光阴最大年夜的身分是物理I/O操作。而限定物理I/O操作此处的最有效措施之一便是应用TOP关键词了。TOP关键词是SQL SERVER中颠末系统优化过的一个用来提取前几条或前几个百分比数据的词。经笔者在实践中的利用,发明TOP确凿很好用,效率也很高。但这个词在别的一个大年夜型数据库ORACLE中却没有,这不能说不是一个遗憾,虽然在ORACLE中可以用其他措施(如:rownumber)来办理。在今后的关于“实现切切级数据的分页显示存储历程”的评论争论中,我们就将用到TOP这个关键词。

到此为止,我们上面评论争论了若何实现从大年夜容量的数据库中快速地查询出您所必要的数据措施。当然,我们先容的这些措施都是“软”措施,在实践中,我们还要斟酌各类“硬”身分,如:收集机能、办事器的机能、操作系统的机能,以致网卡、互换机等。

实现小数据量和海量数据的通用分页显示存储历程

建立一个 Web 利用,分页浏览功能必弗成少。这个问题是数据库处置惩罚中十分常见的问题。经典的数据分页措施是:ADO 记载集分页法,也便是使用ADO自带的分页功能(使用游标)来实现分页。但这种分页措施仅适用于较小数据量的情形,由于游标本身有毛病:游标是寄放在内存中,很费内存。游标一建立,就将相关的记录锁住,直到取消游标。游标供给了对特定聚拢中逐行扫描的手段,一样平常应用游标来逐行遍历数据,根据掏出数据前提的不合进行不合的操作。而对付多表和大年夜表中定义的游标(大年夜的数据聚拢)轮回很轻易使法度榜样进入一个漫长的等待以致逝世机。

更紧张的是,对付异常大年夜的数据模型而言,分页检索时,假如按照传统的每次都加载全部数据源的措施是异常挥霍资本的。现在盛行的分页措施一样平常是检索页面大年夜小的块区的数据,而非检索所有的数据,然后单步履行当前行。

最早较好地实现这种根据页面大年夜小和页码来提取数据的措施大年夜概便是“俄罗斯存储历程”。这个存储历程用了游标,因为游标的局限性,以是这个措施并没有获得大年夜家的普遍认可。

后来,网上有人改造了此存储历程,下面的存储历程便是结合我们的办公自动化实例写的分页存储历程:

CREATE procedure pagination1

(@pagesize int, --页面大年夜小,如每页存储20笔记录

@pageindex int --当前页码

)

as

set nocount on

begin

declare @indextable table(id int identity(1,1),nid int) --定义表变量

declare @PageLowerBound int --定义此页的底码

declare @PageUpperBound int --定义此页的顶码

set @PageLowerBound=(@pageindex-1)*@pagesize

set @PageUpperBound=@PageLowerBound+@pagesize

set rowcount @PageUpperBound

insert into @indextable(nid) select gid from TGongwen

where fariqi >dateadd(day,-365,getdate()) order by fariqi desc

select O.gid,O.mid,O.title,O.fadanwei,O.fariqi from TGongwen O,@indextable t

where O.gid=t.nid and t.id>@PageLowerBound

and t.id

以上存储历程运用了SQL SERVER的最新技巧――表变量。应该说这个存储历程也是一个异常优秀的分页存储历程。当然,在这个历程中,您也可以把此中的表变量写成临时表:CREATE TABLE #Temp。但很显着,在SQL SERVER中,用临时表是没有用表变量快的。以是笔者刚开始应用这个存储历程时,感到异常的不错,速率也比原本的ADO的好。但后来,我又发清楚明了比此措施更好的措施。

笔者曾在网上看到了一篇小短文《从数据表中掏出第n条到第m条的记录的措施》,全文如下:

从publish 表中掏出第 n 条到第 m 条的记录:

SELECT TOP m-n+1 *

FROM publish

WHERE (id NOT IN

(SELECT TOP n-1 id

FROM publish))

id 为publish 表的关键字

我当时看到这篇文章的时刻,真的是精神为之一振,感觉思路异常得好。等到后来,我在作办公自动化系统(ASP.NET+ C#+SQL SERVER)的时刻,溘然想起了这篇文章,我想假如把这个语句改造一下,这就可能是一个异常好的分页存储历程。于是我就满网上找这篇文章,没想到,文章还没找到,却找到了一篇根据此语句写的一个分页存储历程,这个存储历程也是今朝较为盛行的一种分页存储历程,我很忏悔没有争先把这段翰墨改造成存储历程:

CREATE PROCEDURE pagination2

(

@SQL nVARCHAR(4000), --不带排序语句的SQL语句

@Page int, --页码

@RecsPerPage int, --每页容纳的记录数

@ID VARCHAR(255), --必要排序的不重复的ID号

@Sort VARCHAR(255) --排序字段及规则

)

AS

DECLARE @Str nVARCHAR(4000)

SET @Str=''SELECT TOP ''+CAST(@RecsPerPage AS VARCHAR(20))+'' * FROM

(''+@SQL+'') T WHERE T.''+@ID+''NOT IN (SELECT TOP ''+CAST((@RecsPerPage*(@Page-1))

AS VARCHAR(20))+'' ''+@ID+'' FROM (''+@SQL+'') T9 ORDER BY ''+@Sort+'') ORDER BY ''+@Sort

PRINT @Str

EXEC sp_ExecuteSql @Str

GO

着实,以上语句可以简化为:

SELECT TOP 页大年夜小 *

FROM Table1 WHERE (ID NOT IN (SELECT TOP 页大年夜小*页数 id FROM 表 ORDER BY id))

ORDER BY ID

但这个存储历程有一个致命的毛病,便是它含有NOT IN字样。虽然我可以把它改造为:

SELECT TOP 页大年夜小 *

FROM Table1 WHERE not exists

(select * from (select top (页大年夜小*页数) * from table1 order by id) b where b.id=a.id )

order by id

即,用not exists来代替not in,但我们前面已经谈过了,二者的履行效率实际上是没有区其余。既便如斯,用TOP 结合NOT IN的这个措施照样比用游标要来得快一些。

虽然用not exists并不能挽救上个存储历程的效率,但应用SQL SERVER中的TOP关键字却是一个异常明智的选择。由于分页优化的终纵目的便是避免孕育发生过大年夜的记录集,而我们在前面也已经提到了TOP的上风,经由过程TOP 即可实现对数据量的节制。

在分页算法中,影响我们查询速率的关键身分有两点:TOP和NOT IN。TOP可以前进我们的查询速率,而NOT IN会减慢我们的查询速率,以是要前进我们全部分页算法的速率,就要彻底改造NOT IN,同其他措施来替代它。

我们知道,险些任何字段,我们都可以经由过程max(字段)或min(字段)来提取某个字段中的最大年夜或最小值,以是假如这个字段不重复,那么就可以使用这些不重复的字段的max或min作为分水岭,使其成为分页算法平分开每页的参照物。在这里,我们可以用操作符“>”或“Select top 10 * from table1 where id>200

于是就有了如下分页规划:

select top 页大年夜小 *

from table1

where id>

(select max (id) from

(select top ((页码-1)*页大年夜小) id from table1 order by id) as T

)

order by id

在选择即不重复值,又轻易分辨大年夜小的列时,我们平日会选择主键。下表列出了笔者用有着1000万数据的办公自动化系统中的表,在以GID(GID是主键,但并不是凑集索引。)为排序列、提取gid,fariqi,title字段,分手以第1、10、100、500、1000、1万、10万、25万、50万页为例,测试以上三种分页规划的履行速率:(单位:毫秒)

页码

规划1

规划2

规划3

1

60

30

76

10

46

16

63

100

1076

720

130

500

540

12943

83

1000

17110

470

250

10000

24796

4500

140

100000

38326

42283

1553

250000

28140

128720

2330

500000

121686

127846

7168

从上表中,我们可以看出,三种存储历程在履行100页以下的分页敕令时,都是可以相信的,速率都很好。但第一种规划在履行分页1000页以上后,速率就降了下来。第二种规划大年夜约是在履行分页1万页以上后速率开始降了下来。而第三种规划却始终没有大年夜的降势,后劲仍旧很足。

在确定了第三种分页规划后,我们可以据此写一个存储历程。大年夜家知道SQL SERVER的存储历程是事先编译好的SQL语句,它的履行效率要比经由过程WEB页面传来的SQL语句的履行效率要高。下面的存储历程不仅含有分页规划,还会根据页面传来的参数来确定是否进行数据总数统计。

--获取指定页的数据:

CREATE PROCEDURE pagination3

@tblName varchar(255), -- 表名

@strGetFields varchar(1000) = ''*'', -- 必要返回的列

@fldName varchar(255)='''', -- 排序的字段名

@PageSize int = 10, -- 页尺寸

@PageIndex int = 1, -- 页码

@doCount bit = 0, -- 返回记录总数, 非 0 值则返回

@OrderType bit = 0, -- 设置排序类型, 非 0 值则降序

@strWhere varchar(1500) = '''' -- 查询前提 (留意: 不要加 where)

AS

declare @strSQL varchar(5000) -- 主语句

declare @strTmp varchar(110) -- 临时变量

declare @strOrder varchar(400) -- 排序类型

if @doCount != 0

begin

if @strWhere !=''''

set @strSQL = "select count(*) as Total from [" + @tblName + "] where "+@strWhere

else

set @strSQL = "select count(*) as Total from [" + @tblName + "]"

end

--以上代码的意思是假如@doCount通报过来的不是0,就履行总数统计。以下的所有代码都是@doCount为0的环境:

else

begin

if @OrderType != 0

begin

set @strTmp = "

--假如@OrderType不是0,就履行降序,这句很紧张!

end

else

begin

set @strTmp = ">(select max"

set @strOrder = " order by [" + @fldName +"] asc"

end

if @PageIndex = 1

begin

if @strWhere != ''''

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "

from [" + @tblName + "] where " + @strWhere + " " + @strOrder

else

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "

from ["+ @tblName + "] "+ @strOrder

--假如是第一页就履行以上代码,这样会加快履行速率

end

else

begin

--以下代码付与了@strSQL以真正履行的SQL代码

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["

+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["+ @fldName + "])

from (select top " + str((@PageIndex-1)*@PageSize) + " ["+ @fldName + "]

from [" + @tblName + "]" + @strOrder + ") as tblTmp)"+ @strOrder

if @strWhere != ''''

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["

+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["

+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["

+ @fldName + "] from [" + @tblName + "] where " + @strWhere + " "

+ @strOrder + ") as tblTmp) and " + @strWhere + " " + @strOrder

end

end

exec (@strSQL)

GO

上面的这个存储历程是一个通用的存储历程,其注释已写在此中了。 在大年夜数据量的环境下,分外是在查询着末几页的时刻,查询光阴一样平常不会跨越9秒;而用其他存储历程,在实践中就会导致超时,以是这个存储历程异常适用于大年夜容量数据库的查询。 笔者盼望能够经由过程对以上存储历程的解析,能给大年夜家带来必然的启示,并给事情带来必然的效率提升,同时盼望同业提出更优秀的实时数据分页算法。

凑集索引的紧张性和若何选择凑集索引

至此我们评论争论了实现小数据量和海量数据的通用分页显示存储历程。这是由于在将本存储历程利用于“办公自动化”系统的实践中时,笔者发明这第三种存储历程在小数据量的环境下,有如下征象:

1、分页速率一样平常保持在1秒和3秒之间。

2、在查询着末一页时,速率一样平常为5秒至8秒,哪怕分页总数只有3页或30万页。

虽然在超大年夜容量环境下,这个分页的实现历程是很快的,但在分前几页时,这个1-3秒的速率比起第一种以致没有颠末优化的分页措施速率还要慢,借用户的话说便是“还没有ACCESS数据库速率快”,这个熟识足以导致用户放弃应用您开拓的系统。

笔者就此阐发了一下,原本孕育发生这种征象的要害是如斯的简单,但又如斯的紧张:排序的字段不是凑集索引!

本篇文章的题目是:“查询优化及分页算法规划”。笔者只以是把“查询优化”和“分页算法”这两个联系不是很大年夜的论题放在一路,便是由于二者都必要一个异常紧张的器械――凑集索引。

在前面的评论争论中我们已经提到了,凑集索引有两个最大年夜的上风:

1、以最快的速率缩小查询范围。

2、以最快的速率进行字段排序。

第1条多用在查询优化时,而第2条多用在进行分页时的数据排序。

而凑集索引在每个表内又只能建立一个,这使得凑集索引显得加倍的紧张。凑集索引的遴选可以说是实现“查询优化”和“高效分页”的最关键身分。

但要既使凑集索引列既相符查询列的必要,又相符排序列的必要,这平日是一个抵触。笔者前面“索引”的评论争论中,将fariqi,即用户发文日期作为了凑集索引的肇端列,日期的正确度为“日”。这种作法的优点,前面已经提到了,在进行划光阴段的快速查询中,比用ID主键列有很大年夜的上风。

但在分页时,因为这个凑集索引列存在着重复记录,以是无法应用max或min来最为分页的参照物,进而无法实现更为高效的排序。而假如将ID主键列作为凑集索引,那么凑集索引除了用以排序之外,没有任何用场,实际上是挥霍了凑集索引这个宝贵的资本。

为办理这个抵触,笔者后来又添加了一个日期列,其默认值为getdate()。用户在写入记录时,这个列自动写入当时的光阴,光阴正确到毫秒。纵然这样,为了避免可能性很小的重合,还要在此列上创建UNIQUE约束。将此日期列作为凑集索引列。

有了这个光阴型凑集索引列之后,用户就既可以用这个列查找用户在插入数据时的某个光阴段的查询,又可以作为独一列来实现max或min,成为分页算法的参照物。

颠末这样的优化,笔者发明,无论是大年夜数据量的环境下照样小数据量的环境下,分页速率一样平常都是几十毫秒,以致0毫秒。而用日期段缩小范围的查询速率比原本也没有任何痴钝。凑集索引是如斯的紧张和贵重,以是笔者总结了一下,必然要将凑集索引建立在:

1、您最频繁应用的、用以缩小查询范围的字段上;

2、您最频繁应用的、必要排序的字段上。

停止语

本篇文章搜集了笔者近段在应用数据库方面的心得,是在做“办公自动化”系统时实践履历的积累。盼望这篇文章不仅能够给大年夜家的事情带来必然的赞助,也盼望能让大年夜家能够体会到阐发问题的措施;最紧张的是,盼望这篇文章能够抛砖引玉,掀起大年夜家的进修和评论争论的兴趣,以合营匆匆进,合营为公安科技强警奇迹和金盾工程做出自己最大年夜的努力。

着末必要阐明的是,在试验中,我发明用户在进行大年夜数据量查询的时刻,对数据库速率影响最大年夜的不是内存大年夜小,而是CPU。在我的P4 2.4机械上试验的时刻,查看“资本治理器”,CPU常常呈现持续到100%的征象,而内存用量却并没有改变或者说没有大年夜的改变。纵然在我们的HP ML 350 G3办事器上试验时,CPU峰值也能达到90%,一样平常持续在70%阁下。

本文的试验数据都是来自我们的HP ML 350办事器。办事器设置设置设备摆设摆设:双Inter Xeon 超线程 CPU 2.4G,内存1G,操作系统Windows Server 2003 Enterprise Edition,数据库SQL Server 2000 SP3。

您可能还会对下面的文章感兴趣: