数据库索引原理

索引对查询速度有至关重要的影响,如果没有索引,查询将进行整表线性扫描。

  • 选择作为索引的数据类型,越小越简单越好:整型优于字符型存储IP地址,内置的日期/时间数据类型优于字符型。
  • 尽量避免NULL,NULL使得索引、索引的统计信息以及比较运算更加复杂,应该用0、一个特殊的值或者一个空串代替空值。

索引策略

B-Tree索引

聚集索引(Clustered Indexes)

一般通过主键作为聚集索引,将主键存储为B-Tree结构,叶节点存储行数据。因此一个表只能有一个主键。由于B-Tree是平衡树,查找性能大约为logN。查找性能提升,但是为了维护B-Tree,增删改数据时都需要维护索引数据,带来额外的性能开销。

非聚集索引,也就是平常经常提到的常规索引。每次为某个字段建一个索引,字段的数据会被复制一份出来,存储为B-Tree结构,叶节点存储对应的主键。因此,给表添加索引,会增加表的体积,占用磁盘存储空间。通过非聚集索引查找数据,只能查找到对应的主键值,再通过聚集索引查找行数据。

MySQL中,InnoDB按聚集索引的形式存储数据;MyISAM不支持聚集索引,MyISAM按照插入的顺序在磁盘上存储数据,索引中每一个叶子节点仅仅包含行号,这种情况下,主键也只是一个普通的非聚集索引,通过主键访问数据也需要至少两次:先通过B-Tree获取行号,再通过行号读取数据。

覆盖索引(Covering Indexes)

索引包含满足查询的所有数据,则只需要读取索引,不用再通过主键读取数据。由于索引项通常比记录要小,大多数据引擎能更好的缓存索引,因此覆盖索引能提高查询性能。

MySQL仅能对索引最左边的前缀进行有效的查找。假设存在组合索引it_c1_c2(c1,c2),查询语句select * from t1 where c1=1 and c2=2能够使用该索引。查询语句select * from t1 where c1=1也能够使用该索引。但是,查询语句select * from t1 where c2=2不能够使用该索引,因此,列的顺序非常重要 。

Hash索引

MySQL中,只有Memory存储引擎支持hash索引,Memory存储引擎支持非唯一hash索引,如果多个值有相同的hash code,索引把它们的行指针用链表保存到同一个hash表项中,类似Java的HashMap实现。

缺点:不能排序,不支持键部分匹配,只支持等值比较(对于Where price > 100不能加速查询)

Bitmap索引

用位图表示一列,当涉及多列联合查询,只需要对位图作与操作。

MA.Hashing(Multi-attribute Hashing)

取多个字段 hash 值的最后一位,组成一个新的 hash。缺点是:不像单个字段的 hash,永远返回的是一个 page,MA.Hashing会返回多个pages。

Grid Files

将数据按属性分成m*n的表格。

kd-Trees

对条件按照二维空间进行划分。

Quad Trees

和kd-Trees大同小异,对条件按照四维空间进行划分。

利用索引排序

MySQL中,当索引的顺序与ORDER BY中的列顺序相同且所有的列是同一方向(全部升序或者全部降序)时,可以使用索引来排序。如果查询是连接多个表,仅当ORDER BY中的所有列都是第一个表的列时才会使用索引。其它情况都会使用filesort。

filesort利用快速排序在内存中对数据进行排序,如果内存装载不下,它会将磁盘上的数据进行分块,再对各个数据块进行排序,然后将各个块合并成有序的结果集(实际上就是外排序)。 有两种实现方式:

  • 两遍扫描算法(Two passes):先将须要排序的字段和可以直接定位到相关行数据的指针信息取出,然后在设定的内存(通过参数sort_buffer_size设定)中进行排序,完成排序之后再次通过指针信息取出具体数据。MySQL4.1之前采用的算法,它需要两次访问数据,尤其是第二次读取操作会导致大量的随机I/O操作。优点:内存开销较小。
  • 一次扫描算法(single pass):一次性将所需的Columns全部取出,在内存中排序后直接将结果输出。MySQL 4.1版本开始使用该算法。它减少了I/O的次数,效率较高,但是内存开销也较大。如果我们将并不需要的Columns也取出来,就会极大地浪费排序过程所需要的内存。

索引与加锁

索引对于InnoDB非常重要,因为它可以让查询锁更少的元组。这点十分重要,因为MySQL 5.0中,InnoDB直到事务提交时才会解锁。有两个方面的原因:首先,即使InnoDB行级锁的开销非常高效,内存开销也较小,但不管怎么样,还是存在开销。其次,对不需要的元组的加锁,会增加锁的开销,降低并发性。

InnoDB仅对需要访问的元组加锁,而索引能够减少InnoDB访问的元组数。但是,只有在存储引擎层过滤掉那些不需要的数据才能达到这种目的。一旦索引不允许InnoDB那样做(即达不到过滤的目的),MySQL服务器只能对InnoDB返回的数据进行WHERE操作,此时,已经无法避免对那些元组加锁了:InnoDB已经锁住那些元组,服务器无法解锁了。

参考:

深入浅出数据库索引原理

MySql数据库索引原理

数据库查询的索引原理介绍