一、索引的创建与删除
索引的创建有2种方式:
1)自动:当在表上定义主键约束、外键约束、唯一约束的时候,系统会自动为该数据列创建索引
2)手动:通过create index...语句来创建索引
删除索引也有2种方式:
1)自动:数据表被删除的时候,该表上的所以索引将自动删除
2)手动:通过drop index...来删除指定数据列上的索引
二、索引的好处与坏处:
好处:加速查询
坏处:
1)当数据表中的记录被添加、删除、修改的时候,数据库系统需要维护索引,所以会有额外的系统开销
2)存储索引信息需要一定的磁盘空间
三、B-Tree
我们常见的数据库系统,其索引使用的数据结构多是B-Tree或者B+Tree。例如,MsSql使用的是B+Tree,Oracle及Sysbase使用的是B-Tree。所以在最开始,简单地介绍一下B-Tree。
B-Tree不同于Binary Tree(二叉树,最多有两个子树),一棵M阶的B-Tree满足以下条件:
1)每个结点至多有M个孩子;
2)除根结点和叶结点外,其它每个结点至少有M/2个孩子;
3)根结点至少有两个孩子(除非该树仅包含一个结点);
4)所有叶结点在同一层,叶结点不包含任何关键字信息;
5)有K个关键字的非叶结点恰好包含K+1个孩子;
另外,对于一个结点,其内部的关键字是从小到大排序的。以下是B-Tree(M=4)的样例:
对于每个结点,主要包含一个关键字数组Key[],一个指针数组(指向儿子)Son[]。在B-Tree内,查找的流程是:使用顺序查找(数组长度较短时)或折半查找方法查找Key[]数组,若找到关键字K,则返回该结点的地址及K在Key[]中的位置;否则,可确定K在某个Key[i]和Key[i+1]之间,则从Son[i]所指的子结点继续查找,直到在某结点中查找成功;或直至找到叶结点且叶结点中的查找仍不成功时,查找过程失败。
接着,我们使用以下图片演示如何生成B-Tree(M=4,依次插入1~6):
从图可见,当我们插入关键字4时,由于原结点已经满了,故进行分裂,基本按一半的原则进行分裂,然后取出中间的关键字2,升级(这里是成为根结点)。其它的依类推,就是这样一个大概的过程。
四、数据库索引
1.什么是索引
在数据库中,索引的含义与日常意义上的“索引”一词并无多大区别(想想小时候查字典),它是用于提高数据库表数据访问速度的数据库对象。
A)索引可以避免全表扫描。多数查询可以仅扫描少量索引页及数据页,而不是遍历所有数据页。
B)对于非聚集索引,有些查询甚至可以不访问数据页。
C)聚集索引可以避免数据插入操作集中于表的最后一个数据页。
D)一些情况下,索引还可用于避免排序操作。
当然,众所周知,虽然索引可以提高查询速度,但是它们也会导致数据库系统更新数据的性能下降,因为大部分数据更新需要同时更新索引。
2.索引的存储
一条索引记录中包含的基本信息包括:键值(即你定义索引时指定的所有字段的值)+逻辑指针(指向数据页或者另一索引页)。
当你为一张空表创建索引时,数据库系统将为你分配一个索引页,该索引页在你插入数据前一直是空的。此页此时既是根结点,也是叶结点。每当你往表中插入一行数据,数据库系统即向此根结点中插入一行索引记录。当根结点满时,数据库系统大抵按以下步骤进行分裂:
A)创建两个儿子结点
B)将原根结点中的数据近似地拆成两半,分别写入新的两个儿子结点
C)根结点中加上指向两个儿子结点的指针
通常状况下,由于索引记录仅包含索引字段值(以及4-9字节的指针),索引实体比真实的数据行要小许多,索引页相较数据页来说要密集许多。一个索引页可以存储数量更多的索引记录,这意味着在索引中查找时在I/O上占很大的优势,理解这一点有助于从本质上了解使用索引的优势。
3.索引的类型
A)聚集索引,表数据按照索引的顺序来存储的。对于聚集索引,叶子结点即存储了真实的数据行,不再有另外单独的数据页。
B)非聚集索引,表数据存储顺序与索引顺序无关。对于非聚集索引,叶结点包含索引字段值及指向数据页数据行的逻辑指针,该层紧邻数据页,其行数量与数据表行数据量一致。
在一张表上只能创建一个聚集索引,因为真实数据的物理顺序只可能是一种。如果一张表没有聚集索引,那么它被称为“堆集”(Heap)。这样的表中的数据行没有特定的顺序,所有的新行将被添加的表的末尾位置。
4.聚集索引
在聚集索引中,叶结点也即数据结点,所有数据行的存储顺序与索引的存储顺序一致。
1)聚集索引与查询操作
如上图,我们在名字字段上建立聚集索引,当需要在根据此字段查找特定的记录时,数据库系统会根据特定的系统表查找的此索引的根,然后根据指针查找下一个,直到找到。例如我们要查询“Green”,由于它介于[Bennet,Karsen],据此我们找到了索引页1007,在该页中“Green”介于[Greane, Hunter]间,据此我们找到叶结点1133(也即数据结点),并最终在此页中找以了目标数据行。
此次查询的IO包括3个索引页的查询(其中最后一次实际上是在数据页中查询)。这里的查找可能是从磁盘读取(Physical Read)或是从缓存中读取(Logical Read),如果此表访问频率较高,那么索引树中较高层的索引很可能在缓存中被找到。所以真正的IO可能小于上面的情况。
2)聚集索引与插入操作
最简单的情况下,插入操作根据索引找到对应的数据页,然后通过挪动已有的记录为新数据腾出空间,最后插入数据。
如果数据页已满,则需要拆分数据页(页拆分是一种耗费资源的操作,一般数据库系统中会有相应的机制要尽量减少页拆分的次数,通常是通过为每页预留空间来实现):
A)在该使用的数据段(extent)上分配新的数据页,如果数据段已满,则需要分配新段。
B)调整索引指针,这需要将相应的索引页读入内存并加锁。
C)大约有一半的数据行被归入新的数据页中。
D)如果表还有非聚集索引,则需要更新这些索引指向新的数据页。
特殊情况:
A)如果新插入的一条记录包含很大的数据,可能会分配两个新数据页,其中之一用来存储新记录,另一存储从原页中拆分出来的数据。
B)通常数据库系统中会将重复的数据记录存储于相同的页中。
C)类似于自增列为聚集索引的,数据库系统可能并不拆分数据页,页只是简单的新添数据页。
3)聚集索引与删除操作
删除行将导致其下方的数据行向上移动以填充删除记录造成的空白。
如果删除的行是该数据页中的最后一行,那么该数据页将被回收,相应的索引页中的记录将被删除。如果回收的数据页位于跟该表的其它数据页相同的段上,那么它可能在随后的时间内被利用。如果该数据页是该段的唯一一个数据页,则该段也被回收。
对于数据的删除操作,可能导致索引页中仅有一条记录,这时,该记录可能会被移至邻近的索引页中,原索引页将被回收,即所谓的“索引合并”。
5.非聚集索引
非聚集索引与聚集索引相比:
A)叶子结点并非数据结点
B)叶子结点为每一真正的数据行存储一个“键-指针”对
C)叶子结点中还存储了一个指针偏移量,根据页指针及指针偏移量可以定位到具体的数据行。
D)类似的,在除叶结点外的其它索引结点,存储的也是类似的内容,只不过它是指向下一级的索引页的。
聚集索引是一种稀疏索引,数据页上一级的索引页存储的是页指针,而不是行指针。而对于非聚集索引,则是密集索引,在数据页的上一级索引页它为每一个数据行存储一条索引记录。
对于根与中间级的索引记录,它的结构包括:
A)索引字段值
B)RowId(即对应数据页的页指针+指针偏移量)。在高层的索引页中包含RowId是为了当索引允许重复值时,当更改数据时精确定位数据行。
C)下一级索引页的指针
对于叶子层的索引对象,它的结构包括:
A)索引字段值
B)RowId
1)非聚集索引与查询操作
针对上图,如果我们同样查找“Green”,那么一次查询操作将包含以下IO:3个索引页的读取+1个数据页的读取。同样,由于缓存的关系,真实的IO实际可能要小于上面列出的。
2)非聚集索引与插入操作
如果一张表包含一个非聚集索引但没有聚集索引,则新的数据将被插入到最末一个数据页中,然后非聚集索引将被更新。如果也包含聚集索引,该聚集索引将被用于查找新行将要处于什么位置,随后,聚集索引、以及非聚集索引将被更新。
3)非聚集索引与删除操作
如果在删除命令的Where子句中包含的列上,建有非聚集索引,那么该非聚集索引将被用于查找数据行的位置,数据删除之后,位于索引叶子上的对应记录也将被删除。如果该表上有其它非聚集索引,则它们叶子结点上的相应数据也要删除。
如果删除的数据是该数所页中的唯一一条,则该页也被回收,同时需要更新各个索引树上的指针。
由于没有自动的合并功能,如果应用程序中有频繁的随机删除操作,最后可能导致表包含多个数据页,但每个页中只有少量数据。
6.索引覆盖
索引覆盖是这样一种索引策略:当某一查询中包含的所需字段皆包含于一个索引中,此时索引将大大提高查询性能。
包含多个字段的索引,称为复合索引。索引最多可以包含31个字段,索引记录最大长度为600B。如果你在若干个字段上创建了一个复合的非聚集索引,且你的查询中所需Select字段及Where,Order By,Group By,Having子句中所涉及的字段都包含在索引中,则只搜索索引页即可满足查询,而不需要访问数据页。由于非聚集索引的叶结点包含所有数据行中的索引列值,使用这些结点即可返回真正的数据,这种情况称之为“索引覆盖”。
在索引覆盖的情况下,包含两种索引扫描:
A)匹配索引扫描
B)非匹配索引扫描
1)匹配索引扫描
此类索引扫描可以让我们省去访问数据页的步骤,当查询仅返回一行数据时,性能提高是有限的,但在范围查询的情况下,性能提高将随结果集数量的增长而增长。
针对此类扫描,索引必须包含查询中涉及的的所有字段,另外,还需要满足:Where子句中包含索引中的“引导列”(Leading Column),例如一个复合索引包含A,B,C,D四列,则A为“引导列”。如果Where子句中所包含列是BCD或者BD等情况,则只能使用非匹配索引扫描。
2)非配置索引扫描
正如上述,如果Where子句中不包含索引的导引列,那么将使用非配置索引扫描。这最终导致扫描索引树上的所有叶子结点,当然,它的性能通常仍强于扫描所有的数据页。
7、复合索引
索引可以包含一个、两个或更多个列。两个或更多个列上的索引被称作复合索引。例如,以下语句创建一个具有两列的复合索引:
CREATE INDEX name ON employee (emp_lname, emp_fname)
如果第一列不能单独提供较高的选择性,复合索引将会非常有用。例如,当许多雇员具有相同的姓氏时,emp_lname 和 emp_fname 上的复合索引非常有用。因为每个雇员都有唯一的 ID,所以 emp_id 和 emp_lname 上的复合索引可能没有用处,因此列 emp_lname 不会提供任何附加选择性。
利用索引中的附加列,您可以缩小搜索的范围,但使用一个具有两列的索引不同于使用两个单独的索引。复合索引的结构与电话簿类似,它首先按姓氏对雇员进行排序,然后按名字对所有姓氏相同的雇员进行排序。如果您知道姓氏,电话簿将非常有用,如果您知道名字和姓氏,电话簿则更为有用,但如果您只知道名字而不知道姓氏,电话簿将没有用处。
压缩的 B 树索引方法可显著提高复合索引的性能。
列顺序
在创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。
如果您很可能仅对一个列多次执行搜索,则该列应该是复合索引中的第一列。如果您很可能对一个两列索引中的两个列执行单独的搜索,则应该创建另一个仅包含第二列的索引。
包含多个列的主键始终会自动以复合索引的形式创建索引,其列的顺序是它们在表定义中出现的顺序,而不是在主键定义中指定的顺序。您应该考虑将通过主键来执行的搜索,以确定哪一列应该排在最前面。在后面的任何被频繁搜索的主键列上,应该考虑添加额外的索引。
例如,假设您在两个列上创建一个复合索引。一个列包含雇员的名字,另一个列包含雇员的姓氏。您可以创建一个先包含名字后包含姓氏的索引。或者,您也可以创建一个先包含姓氏后包含名字的索引。虽然这两个索引以两个列组织信息,但它们具有不同的功能。
CREATE INDEX fname_lname ON employee emp_fname, emp_lname; CREATE INDEX lname_fname ON employee emp_lname, emp_fname;
假设您需要搜索名字 John。唯一有用的索引是在索引的第一列包含名字的索引。由于名字为 John 的雇员会出现在索引中的任意位置,因此先按姓氏再按名字组织的索引没有用处。
如果您认为很可能需要仅按名字或仅按姓氏查找雇员,则应该创建这两个索引。
或者,您也可以创建两个索引,一个索引仅包含一个列。但是,请注意,Adaptive Server Anywhere 在处理单个查询时只使用一个索引来对任何一个表进行访问。即使您知道名字和姓氏,Adaptive Server Anywhere 也可能需要读取额外的行,以查找包含正确姓氏的行。
当您使用 CREATE INDEX 命令创建索引时(如上例所示),列会按命令中所示的顺序创建。
主键索引和列顺序
列在主键索引中的顺序被强制为与列在表定义中出现的顺序相同,这与 PRIMARY KEY 约束中指定的列顺序无关。此外,Adaptive Server Anywhere 还将强制一个附加约束:表的主键列必须位于每个行的开头。因此,如果主键被添加到现有表中,服务器就可以重写整个表以确保键列位于每个行的开头。
在多个列出现在主键中的情况下,您应该考虑所需的搜索类型。如果合适,应切换列在表定义中的顺序,使最常搜索的列排在最前面,或者根据需要为其它列创建单独的索引。
复合索引和 ORDER BY
缺省情况下,索引的列按升序排列,但您可以选择通过在 CREATE INDEX 语句中指定 DESC 来将这些列按降序排列。
只要 ORDER BY 子句仅包含索引中的列,Adaptive Server Anywhere 就可以选择使用索引来优化 ORDER BY 查询。此外,索引列的排序方式必须与 ORDER BY 子句完全相同或完全相反。对于单列索引,这种排序方式始终会使查询可以得到优化,但复合索引则需要稍微多考虑一些问题。下表显示了一个两列索引的可能性。
索引列 | 可优化的 ORDER BY 查询 | 不可优化的 ORDER BY 查询 |
---|---|---|
ASC、ASC | ASC、ASC 或 DESC、DESC | ASC、DESC 或 DESC、ASC |
ASC、DESC | ASC、DESC 或 DESC、ASC | ASC、ASC 或 DESC、DESC |
DESC、ASC | DESC、ASC 或 ASC、DESC | ASC、ASC 或 DESC、DESC |
DESC、DESC | DESC、DESC 或 ASC、ASC | ASC、DESC 或 DESC、ASC |
含有两个以上的列的索引遵循与上述规则相同的一般规则。例如,假设您具有以下索引:
CREATE INDEX idx_example ON table1 (col1 ASC, col2 DESC, col3 ASC)
在这种情况下,以下查询可以得到优化:
SELECT col1, col2, col3 from table1 ORDER BY col1 ASC, col2 DESC, col3 ASC
和
SELECT col1, col2, col3 from example ORDER BY col1 DESC, col2 ASC, col3 DESC
索引不用于优化在 ORDER BY 子句中具有 ASC 和 DESC 的其它任何模式的查询。例如:
SELECT col1, col2, col3 from table1 ORDER BY col1 ASC, col2 ASC, col3 ASC
不会得到优化。
更多的内容请参考: