《索引的基本原理.doc》由会员分享,可在线阅读,更多相关《索引的基本原理.doc(8页珍藏版)》请在金锄头文库上搜索。
1、索引的基本原理,以及数据是如何被访问的(一)SQLS如何访问没有建立索引的数据表Heap译成汉语叫做“堆”,其本义暗含杂乱无章、无序的意思,前面提到数据值被写进数据页时,由于每一行记录之间并没有特定的排列顺序,所以行与行的顺序就是随机无序的,当然表中的数据页也就是无序的了,而表中所有数据页就形成了“堆”。可以说,一张没有索引的数据表,就像一个只有书柜而没有索引卡片柜的图书馆,书库里面塞满了一堆乱七八糟的图书。当读者对管理员提交查询请求后,管理员就一头钻进书库,对照查找内容从头开始一架一柜的逐本查找。运气好的话,在第一个书架的第一本书就找到了,运气不好的话,要到最后一个书架的最后一本书才找到。S
2、QLS在接到查询请求时,首先会分析sysindexes表中一个叫做索引标志符(INDID: Index ID)的字段的值,如果该值为0,表示这是一张数据表而不是索引表,SQLS就会使用sysindexes表的另一个字段也就是在前面提到过的FirstIAM值中找到该表的IAM页链,也就是所有数据页集合。这就是对一个没有建立索引的数据表进行数据查找的方式,是不是很没效率?对于没有索引的表,对于一“堆”这样的记录,SQLS也只能这样做,而且更没劲的是,即使在第一行就找到了被查询的记录,SQLS仍然要从头到尾的将表扫描一次。这种查询称为“遍历”,又叫“表扫描”。可见没有建立索引的数据表照样可以运行,不
3、过这种方法对于小规模的表来说没有什么太大的问题,但要查询海量的数据效率就太低了。(二)SQLS如何访问建立了非聚集索引的数据表如前所述,非聚集索引可以建多个,具有B树结构,其叶级节点不包含数据页,只包含索引行。假定一个表中只有非聚集索引,则每个索引行包含了非聚集索引键值以及行定位符(ROW ID,RID),他们指向具有该键值的数据行,每一个RID由文件ID、页编号和在页中行的编号组成。当INDID的值在2至250之间时,意味着表中存在非聚集索引页。此时,SQLS调用ROOT字段的值指向非聚集索引B树的ROOT,在其中查找与被查询最相近的值,根据这个值找到在非叶级节点中的页号,然后顺藤摸瓜,在叶
4、级节点相应的页面中找到该值的RID,最后根据这个RID在Heap中定位所在的页和行并返回到查询端。例如:假定在Lastname上建立了非聚集索引,则执行Select * From Member Where Lastname=Ota时,查询过程是:SQLS查询INDID值为2;立即从根出发,在非叶级节点中定位最接近Ota的值“Martin”,并查到其位于叶级页面的第61页;仅在叶级页面的第61页的Martin下搜寻Ota的RID,其RID显示为N7064,表示Lastname字段中名为Ota的记录位于堆的第706页的第4行,N表示文件的ID值,与数据无关;根据上述信息,SQLS立刻在堆的第706
5、页第4行将该记录“揪”出来并显示于前台(客户端)。视表的数据量大小,整个查询过程费时从百分之几毫秒到数毫秒不等。在谈到索引基本概念的时候,我们就提到了这种方式:图书馆的前台有很多索引卡片柜,里面分了若干的类别,诸如按照书名笔画或拼音顺序、作者笔画或拼音顺序等,但有两点不同之处: 索引卡片上记录了每本书摆放的具体位置位于某柜某架的第几本而不是“特殊编号”; 书脊上并没有那个“特殊编号”。管理员在索引柜中查到所需图书的具体位置(RID)后,根据RID直接在书库中的具体位置将书提出来。显然,这种查询方式效率很高,但资源占用极大,因为书库中书的位置随时在发生变化,必然要求管理员花费额外的精力和时间随时
6、做好索引更新。(三)SQLS如何访问建立聚集索引的数据表在聚集索引中,数据所在的数据页是叶级,索引数据所在的索引页是非叶级。查询原理和上述对非聚集索引的查询相似,但由于记录是按照聚集索引中索引键值进行排序,换句话说,聚集索引的索引键值也就是具体的数据页。这就好比书库中的书就是按照书名的拼音在排序,而且也只按照这一种排序方式建立相应的索引卡片,于是查询起来要比上述只建立非聚集索引的方式要简单得多。仍以上面的查询为例:假定在Lastname字段上建立了聚集索引,则执行Select * From Member Where Lastname=Ota时,查询过程是:SQLS查询INDID值为1,这是在系
7、统中只建立了聚集索引的标志;立即从根出发,在非叶级节点中定位最接近Ota的值“Martin”,并查到其位于叶级页面的第120页;在位于叶级页面第120页的Martin下搜寻到Ota条目,而这一条目已是数据记录本身;将该记录返回客户端。这一次的效率比第二种方法更高,以致于看起来更美,然而它最大的优点也恰好是它最大的缺点由于同一张表中同时只能按照一种顺序排列,所以在任何一种数据表中的聚集索引只能建立一个;并且建立聚集索引需要至少相当于源表120%的附加空间,以存放源表的副本和索引中间页。难道鱼和熊掌就不能兼顾了吗?办法是有的。(四)SQLS如何访问既有聚集索引、又有非聚集索引的数据表如果我们在建立
8、非聚集索引之前先建立了聚集索引的话,那么非聚集索引就可以使用聚集索引的关键字进行检索。就像在图书馆中,前台卡片柜中可以有不同类别的图书索引卡,然而每张卡片上都载明了那个特殊编号并不是书籍存放的具体位置。这样在最大程度上既照顾了数据检索的快捷性,又使索引的日常维护变得更加可行,这是最为科学的检索方法。也就是说,在只建立了非聚集索引的情况下,每个叶级节点指明了记录的行定位符(RID);而在既有聚集索引又有非聚集索引的情况下,每个叶级节点所指向的是该聚集索引的索引键值,即数据记录本身。假设聚集索引建立在Lastname上,而非聚集索引建立在Firstname上,当执行Select * From Me
9、mber Where Firstname=Mike时,查询过程是:SQLS查询INDID值为2;立即从根出发,在Firstname的非聚集索引的非叶级节点中定位最接近Mike的值“Jose”条目;从Jose条目下的叶级页面中查到Mike逻辑位置不是RID而是聚集索引的指针;根据这一指针所指示位置,直接进入位于Lastname的聚集索引中的叶级页面中到达Mike数据记录本身;将该记录返回客户端。这就完全和我们在“索引的基本概念”中讲到的现实场景完全一样了,当数据发生更新的时候,SQLS只负责对聚集索引的键值加以维护,而不必考虑非聚集索引。只要我们在ID类的字段上建立聚集索引,而在其它经常需要查询
10、的字段上建立非聚集索引,通过这种科学的、有针对性的在一张表上分别建立聚集索引和非聚集索引的方法,我们既享受了索引带来的灵活与快捷,又相对避免了维护索引所导致的大量的额外资源消耗。索引的优点和不足索引有一些先天不足1、系统要占用大约为表的1.2倍的硬盘和内存空间来保存索引;2、更新数据的时候,系统必须要有额外的时间来同时对索引进行更新,以维持数据和索引的一致性。当然建立索引的优点也是显而易见的,在海量数据的情况下,如果合理的建立了索引,则会大大加强SQLS执行查询、对结果进行排序、分组的操作效率。实践表明,不恰当的索引不但于事无补,反而会降低系统性能。因为大量的索引在进行插入、修改和删除操作时比
11、没有索引要花费更多的系统时间。在如下字段建立索引应该是不恰当的:1、很少或从不引用的字段;2、逻辑型的字段,如男或女(是或否)等。综上所述,提高查询效率是以消耗一定的系统资源为代价的,索引不能盲目的建立,必须要有统筹的规划,一定要在“加快查询速度”与“降低修改速度”之间做好平衡。有得必有失,此消则彼长,这是考验一个DBA是否优秀的很重要的指标建立索引时一定要在“加快查询速度”与“降低修改速度”之间做好平衡,有得必有失,此消则彼长。那么,SQLS维护索引时究竟怎样消耗资源?应该从哪些方面对索引进行管理与优化?以下从六个方面来回答这些问题。 一页分裂 微软MOC教导我们:当一个数据页达到了8K容量
12、,如果此时发生插入或更新数据的操作,将导致页的分裂(又名页拆分): 1有聚集索引的情况下:聚集索引将被插入和更新的行指向特定的页,该页由聚集索引关键字决定; 2只有堆的情况下:只要有空间就可以插入新的行,但是如果我们对行数据的更新需要更多的空间,以致大于当前页的可用空间,行就被移到新的页中,并且在原位置留下一个转发指针,指向被移动的新行,如果具有转发指针的行又被移动了,那么原来的指针将重新指向新的位置; 3如果堆中有非聚集索引,那么尽管插入和更新操作在堆中不会发生页分裂,但是在非聚集索引上仍然产生页分裂。 无论有无索引,大约一半的数据将保留在老页面,而另一半将放入新页面,并且新页面可能被分配到
13、任何可用的页。所以,频繁页分裂,后果很严重,将使物理表产生大量数据碎片,导致直接造成I/O效率的急剧下降,最后,不得不停止SQLS的运行并重建索引。 二填充因子 然而在“混沌之初”,就可以在一定程度上避免不愉快出现,在创建索引时,可以为这个索引指定一个填充因子,以便在索引的每个叶级页面上保留一定百分比的空间,将来数据可以进行扩充和减少页分裂。填充因子是从0到100的百分比数值,设为100时表示将数据页填满,只有当不会对数据进行更改时(例如只读表中)才用此设置。值越小则数据页上的空闲空间越大,这样可以减少在索引增长过程中进行页分裂的需要,但这一操作需要占用更多的硬盘空间。 填充因子只在创建索引时
14、执行,索引创建以后,当表中进行数据的添加、删除或更新时,是不会保持填充因子的,如果想在数据页上保持额外的空间,则有悖于使用填充因子的本意,因为随着数据的输入,SQLS必须在每个页上进行页拆分,以保持填充因子指定的空闲空间。因此,只有在表中的数据进行了较大的变动,才可以填充数据页的空闲空间。这时,可以从容的重建索引,重新指定填充因子,重新分布数据。 反之,填充因子指定不当,就会降低数据库的读取性能,其降低量与填充因子设置值成反比。例如,当填充因子的值为50时,数据库的读取性能会降低两倍。所以,只有在表中根据现有数据创建新索引,并且可以预见将来会对这些数据进行哪些更改时,设置填充因子才有意义。 三
15、两道数学题 假定数据库设计没有问题,那么是否像上篇分析的那样,当你建立了众多的索引,在查询工作中SQLS就只能按照“最高指示”用索引处理每一个提交的查询呢?答案是否定的。实际上,SQLS几乎完全是“自主”的决定是否使用索引或使用哪一个索引。 这是怎么回事呢? 让我们先来算一道题:如果某表的一条记录在磁盘上占用1000字节(1K)的话,我们对其中10字节的一个字段建立索引,那么该记录对应的索引大小只有10字节(0.01K)。上篇说过,SQLS的最小空间分配单元是“页(Page)”,一个页面在磁盘上占用8K空间,所以一页只能存储8条“记录”,但可以存储800条“索引”。现在我们要从一个有8000条记录的表中检索符合某个条件的记录(有Where子句),如果没有索引的话,我们需要遍历8000条1000字节/8K字节=1000个页面才能够找到结果。如果在检索字段上有上述索引的话,那么我们可以在8000条10字节/8K字节=10个页面中