众所周知,索引对于数据库性能的影响至关重要,但是索引为什么可以提高查询效率,以及索引的种类及其特点可能不是很清楚,本文将对常用的索引类型以及特点做一个简单的介绍
首先来说一下索引为什么可以提高查询效率。普通查询的过程往往是通过整表的扫描来获得期望的结果,如果表的纪录非常的多,查询的效率肯定会很慢。而索引则会通过最大程度的降低扫描纪录的条数来提高效率,不同类型的索引往往会采取不同的策略来降低扫描的记录数,具体的策略将在后面进行描述。
首先看一个简单的例子,来说明索引的作用
在这个例子中使用了一张包含100,000条左右的字典表 ,比较是否包含索引的查询时间
mysql> select id,word, mean from dictionary where mean='DEFAULT2'; +--------+--------+----------+ | id | word | mean | +--------+--------+----------+ | 110003 |Random | DEFAULT2 | +--------+--------+----------+ 1 row inset (0.05sec) mysql> select id,word, mean from dictionary where word='Random'; +--------+--------+----------+ | id | word | mean | +--------+--------+----------+ | 110004 |Random | DEFAULR# | | 110003 |Random | DEFAULT2 | +--------+--------+----------+ 2 rows inset (0.00sec)
接下来看看为什么会时间上有所差别,通过执行计划可以看出,第一条语句执行了整表扫描,查询了110486条记录才得到想要的结果,而第二条语句使用了索引,只检索了2条记录就得到了想要的结果,这说明了索引的主要提速原理:查询的过程中减少扫描的记录数
mysql> explain select id,word, mean from dictionary wheremean='DEFAULT2'; +----+-------------+------------+-------+---------------+------+---------+------+--------+--------------------------+ | id |select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+---------------+------+---------+------+--------+--------------------------+ | 1 | SIMPLE | dictionary | All | NULL | word | 135 | NULL | 110486 | Using where; Usingindex | +----+-------------+------------+-------+---------------+------+---------+------+--------+--------------------------+ 1 row inset (0.00 sec) mysql> explain select id,word, mean from dictionary where word='Random'; +----+-------------+------------+------+---------------+------+---------+-------+------+--------------------------+ | id |select_type | table | type |possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+------+---------------+------+---------+-------+------+--------------------------+ | 1 | SIMPLE | dictionary | ref | word | word | 102 | const | 2| Usingwhere; Using index | +----+-------------+------------+------+---------------+------+---------+-------+------+--------------------------+ 1 row inset (0.00 sec)
在大多数的RDBMS中,索引的特性由存储引擎决定,不同的存储引擎在索引的实现上可能会采用不同的实现,B-Tree Index以及Hash Index是比较常用的两种索引。这两种索引使用的底层数据结构是不同的,所以这两种索引在使用的过程中也有各自的特点
B-Tree索引是一种使用相对广泛的索引类型,在很多数据库中 (ORACLE,MYSQL) 也将它作为默认的索引类型,这种索引采用B-Tree数据结构来存储数据。
B-tree是以排序的方式存储数据并允许以O(log n)的运行时间进行查找,顺序读取,插入和删除的数据结构。概括来说是一个节点可以拥有多于2个子节点的二叉查找树。在B-Tree中,内部(非叶子)节点可以拥有,预先设定范围数量内的多个子节点。当数据被插入或从一个节点中移除,它的子节点数量发生变化。
下面是B-Tree的结构图
上图说明了B-Tree的工作原理,在根节点中定义了叶子节点值的区间范围,叶子中存储了实际的值。当进行查找时,首先会使用条件值在根节点中选择一个合适叶子节点区间,然后再用条件值和叶子层某个区间内的叶子节点的值进行比较。
举个例子来说明其原理,例如 学生表中的学生ID是有序递增的,图中的Key1 是100,Key2是200.当需要查询一个ID为90的学生时会在最左侧的叶子链表中进行搜索,如果需要查询一个ID为130的学生时,会在中间的叶子链表中进行查找。这样的查询方式因为避免了全表扫描,所以效率会大大的提高。
有一点需要注意,当把B-Tree索引建立在多个字段上时,(例如 建立索引时顺序为 LastName, FirstName,BrithDay),则每个Key值都是LastName,FirstName,Brithday这样的数据结构,匹配的叶子节点值的过程是按照索引中定义的字段顺序来进行比较的,所以在使用索引的过程中必须按照这样的顺序来使用,否则索引将得不到正确使用(比如你在Where条件中的顺序是Brithday , LastName, FirstName)。
由于在B-Tree中存储的索引数据都是有序的,如果在B-Tree索引上执行Order by,排序的效率也会大大的提高。
B-Tree的工作原理决定了它对下面的查询方式有良好的支持:
(1) 全索引匹配- 匹配条件包含索引的所有字段,以及完全匹配其字段顺序
(2) 只匹配索引的第一列
(3) 只匹配第一列的前缀(右匹配),例如 “where lastName like Sun%”
(4) 第一列的范围查找 –例如 “where lastName between “Steve” and “Tony”
(5) 第一列全匹配,第二列前缀匹配
(6) 要求返回的值,是索引的子集,例如 select LastName, FristName,Brithday from Student where LastName like ”Tony”. 因为B-Tree中包含了要求的值,所以在这种情况下可以让数据的访问只发生在B-Tree中而避免对数据表的访问(Mysql中有个专门的名词叫“覆盖索引”)
同时B-Tree的工作原理也决定了在使用下面的查询方式时,索引的功效会受到影响:
(1) 查询条件没有从索引的第一列开始,例如 where firstname=”Eric” andbirthday=’2010-10-10’
(2) 没有顺序的使用索引中的列,例如 where lastname=”Tony” andbirthday=”2010-10-10”
(3) 由于使用了模糊匹配,导致了值使用了索引的部分字段,例如 where lastname=’tony’ andfirstname like ‘Robert%’ and birthday=’2010-10-10’, 在这里只用到了索引的lastname以及firstname字段,brithday被like 操作给屏蔽掉了
前面列出了B-Tree索引在使用的过程中的一些问题,这些问题说明查询条件中字段的顺序对索引的使用会有比较大的影响。所以在设计索引或者查询条件时要注意字段的顺序问题。有些时候可能还会建立多个字段相同但是顺序不同的索引来弥补这种顺序问题。
顾名思义,这种类型的索引采取Hash的数据结构来存储索引。结构图大概为
存储的时候会把key通过Hash函数计算,得到key的Hash值,再用这个Hash值做指针和数据库记录指针绑定在一起。选定一个好的Hash函数很重要,好的Hash函数可以使计算出的Hash值分布均匀,降低冲突,只有冲突减小了,才会降低Hash表的查找时间。在查询的过程大概会分为四步
(1) 根据查询条件生成一个Hash值例如 在name 上建立了一个hash索引,且在查询条件where name=’John Smith’ 中’John Smith’的hash值是02.
(2) 用02的Hash值到Hash索引表中找到对应的Bucket
(3) 使用步骤(2)中Bucket包含的表指针(521-1234)找到数据库中的某条记录
(4) 由于不同的name可能会有相同的Hash值,所以最后一步需要比较’John Smith’是否和已经找到的数据库记录的name相同,相同就返回当前记录,否则返回步骤2,寻找另外一条数据记录再进行匹配,直到找到对应的记录
Hash 索引结构的特殊性,决定了其检索效率非常的高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引。
可能很多人又有疑问了,既然 Hash 索引的效率要比 B-Tree 高很多,为什么大家不都用Hash 索引而还要使用 B-Tree 索引呢?任何事物都是有两面性的,Hash 索引也一样,虽然 Hash 索引效率高,但是 Hash索引本身由于其特殊性也带来了很多限制和弊端,主要有以下这些。
(1)Hash 索引仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询。
由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和Hash运算前完全一样。
(2)Hash 索引无法被用来避免数据的排序操作。
由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值,而且Hash值的大小关系并不一定和 Hash 运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算;
(3)Hash 索引不能利用部分索引键查询。
对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用。
(4)Hash 索引在任何时候都不能避免表扫描。
前面已经知道,Hash 索引是将索引键通过 Hash 运算之后,将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。
(5)Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。
对于选择性比较低的索引键,如果创建 Hash 索引,那么将会存在大量记录指针信息存于同一个 Hash 值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下。
值得一提的是,多数的数据库管理系统默认的索引类型为B-Tree(Oracle,Mysql-InnoDB),所以想要使用Hash索引的话,必须显示的设定其为Hash索引。很多比较智能的数据存储引擎(例如 Mysql 的InnoDB)会采用一种叫做“自适应Hash索引”来提高查询效率,这种机制的工作原理是 当存储引擎使用B-Tree的索引类型时,如果发现某个索引的值被检索的非常频繁时,存储引擎会自动把该值当做Hash处理,以此来提高B-Tree的效率。