#索引与算法
InnoDB 存储引擎支持以下几种常见的索引
略
B+ 树由B 树和索引顺序访问方法(ISAM)演化而来, B+ 树是为磁盘或其他直接存取辅助设备设计的一种平衡查找树。在B+ 树中,所有记录节点都是按键值的大小顺序存放在同一层的叶子节点上,由各叶子节点指针进行连接。先来看一个B+ 树,其高度为2 ,每页可存放4 条记录,扇出(fan out) 为5,所有记录都在叶子节点上,并且是顺序存放的,如果用户从最左边的叶子节点开始顺序遍历,可以得到所有键值的顺序排序: 5 、10 、15 、20 、25 、30 、50 、55 、60 、65 、75 、80 、85 、90 。
B+ 树的插入必须保证插入后叶子节点中的记录依然排序,同时需要考虑插入到B+树的三种情况,每种情况都可能会导致不同的插入算法。
旋转发生在Leaf Page 已经满,但是其的左右兄弟节点没有满的情况下。这时B+ 树并不会急于去做拆分页的操作,而是将记录移到所在页的兄弟节点上。
B+ 树使用填充因子(fill factor) 来控制树的删除变化, 50% 是填充因子可设的最小值。B+ 树的删除操作同样必须保证删除后叶子节点中的记录依然排序,同插入一样,B+ 树的删除操作同样需要考虑以下表5-2 中的三种情况,与插入不同的是,删除根据填充因子的变化来衡量。
B+ 树索引的本质就是B+ 树在数据库中的实现。但是B+ 索引在数据库中有一个特点是高扇出性,因此在数据库中, B+树的高度一般都在2 - 4 层,这也就是说查报一键值的行记录时最多只2-4次。
数据库中的B+ 树索引可以分为聚集索引(clustered inex) 和辅助索引(secondary index) ,但是不管是聚集还是辅助的索引,其内部都是B+ 树的,即高度平衡的,叶子节点存放着所有的数据。聚集索引与辅助索引不同的是,叶子节点存放的是否是一整行的信息。
InnoDB 存储引肇表是索引组织表,即表中数据按照主键顺序,存放。而聚集索引(clustered index) 就是按照每张表的主键构造一棵B+ 树,同时叶子节点中存放的即为整张表的行记录数据,也将聚集索引的叶子节点称为数据页。聚集索引的这个特性决定了索引组织表中数据也是索引的一部分。同B+ 树数据结构一样,每个数据页都通过一个双向链表来进行链接。
由于实际的数据页只能按照一棵B+ 树进行排序,因此每张表只能拥有一个聚集索引。在多数情况下,查询优化器倾向于采用聚集索引。因为聚集索引能够在B+ 树索引的叶子节点上直接找到数据。此外,由于定义了数据的逻辑顺序,聚集索引能够特别快地访问针对范围值的查询。查询优化器能够快速发现某一段范围的数据页需要扫描。
聚集索引的存储并不是物理上连续的,而是逻辑上连续的。这其中有两点: 一是前面说过的页通过双向链表链接,页按照主键的
顺序排序:另一点是每个页中的记录也是通过双向链表进行维护的,物理存储上可以同样不按照主键存储。
聚集索引的另一个好处是,它对于主键的排序查找和范围查找速度非常快。叶子节点的数据就是用户所要查询的数据。如用户需要查询一张注册用户的表,查询最后注册的10 位用户,由于B+ 树索引是双向链表的,用户可以快速找到最后一个数据页,并取出10 条记录。
对于辅助索引(Secondary Index,也称非聚集索引), 叶子节点并不包含行记录的全部数据。叶子节点除了包含键值以外, 每个叶子节点中的索引行中还包含了一个书签(bookmark) 。该书签用来告诉InnoDB 存储引擎哪里可以找到与索引相对应的行数据。由于InnoDB 存储引擎表是索引组织表, 因此InnoDB 存储引擎的辅助索引的书签就是相应行数据的聚集索引键。
辅助索引的存在并不影响数据在聚集索引中的组织,因此每张表上可以有多个辅助索引。当通过辅助索引来寻找数据时, lnnoDB 存储引擎会遍历辅助索引并通过叶级别的指针获得指向搜索引的主键,然后再通过主键索引来找到他。
略
1. 索引管理
索引的创建和删除可以通过两种方法, 一种是ALTER TABLE ,另一种是CREATE/DROPINDEX
并不是在所有的查询条件中出现的列都需要添加索引。对于什么时候添加B+ 树索引,在访问表中很少一部分时使用叫索引才有意义,就是有区分度的列。
可以通过SHOW 时DEX 结果中的列Cardinality来观察。Cardinality 值非常关键,表示索引中不重复记录数量的预估值。同时需要注意的是, Cardinality 是一个预估值,而不是一个准确值,基本上用户也不可能得到一个准确的值。在实际应用中, Cardinality/o_rows_io_table 应尽可能地接近1 。如果非常小,那么用户需要考虑是否还有必要创建这个索引。故在访问高选择性属性的字段并从表中取出很少一部分数据时,对这个字段添加B+ 树索引是非常有必要的。
在InnoDB 存储引擎中, Cardinality 统计信息的更新发生在两个操作中: lNSERT和UPDATE。 不可能在每次发生lNSERT 和UPDATE 时就去更新Cardinality 信息,这样会增加数据库系统的负荷,同时对于大表的统计,时间上也不允许数据库这样去操作。因此, InnoDB 存储引擎内部对更新Cardinality可信息的策略为:
需要根据自己的具体生产环境来使用索引,并观察索引使用的情况,判断是否需要添加索引。区分OLTP 和OLAP应用。
在OLTP 应用中, 查询操作只从数据库中取得一小部分数据,一般可能都在10 条记录以下,甚至在很多时候只取1 条记录,如根据主键值来取得用户信息,根据订单号取得订单的详细信息,这都是典型OLTP 应用的查询语句。在这种情况下, B+ 树索引建立后,对该索引的使用应该只是通过该索引取得表中少部分的数据。这时建立B+ 树索引才是有意义的,否则即使建立了,优化器也可能选择不使用索引。
对于OLAP 应用,在OLAP 应用中,都需要访问表中大量的数据,根据这些数据来产生查询的结果,这些查询多是面向分析的查询,目的是为决策者提供支持。如这个月每个用户的消费情况,销售额同比、环比增长的情况。因此在OLAP 中索引的添加根据的应该是宏观的信息,而不是微观, 因为最终要得到的结果是提供给决策者的。例如不需要在OLAP 中对姓名字段进行索引,因为很少需要对单个用户进行查询。但是对于OLAP 中的复杂查询,要涉及多张表之间的联接操作,因此索引的添加依然是有意义的。但是,如果联接操作使用的是Hash Join,那么索引可能又变得不是非常重要了,所以这需要DBA 或开发人员认真并仔细地研究自己的应用。不过在OLAP 应用中,通常会需要对时间字段进行索引,这是因为大多数统计需要根据时间维度来进行数据的筛选。
联合索引是指对表上的多个列进行索引。前面讨论的情况都是只对表上的一个列进行索引。联合索引的创建方法与单个索引创建的方法一样,不同之处仅在于有多个索引列。
假设这是一个多列索引(col1, col2,col3),对于叶子节点,是这样的:
也就是说,联合索引(col1, col2,col3)也是一棵B+Tree,其非叶子节点存储的是第一个关键字的索引,而叶节点存储的则是三个关键字col1、col2、col3三个关键字的数据,且按照col1、col2、col3的顺序进行排序。
从本质上来说,联合索引也是一棵B+ 树,不同的是联合索引的键值的数量不是1.而是大于等于2 。接着来讨论两个整型列组成的联合索引,假定两个键值的名称分别为a、b ,如图
可以观察到多个键值的B+ 树情况。其实和之前讨论的单个键值的B+ 树并没有什么不同,键值都是排序的,通过叶子节点可以逻辑上顺序地读出所有数据,就上面的例子来说,即(1, 1 ) 、(1, 2) 、(2 , 1 ) 、(2 , 4 ) 、(3, 1 ) 、(3 . 2 ) 。数据按(a,b ) 的顺序进行了存放。
因此,对于查询SELECT • FROM TABLE WHERE a=xxx and b=xxx,显然是可以使用( a. b ) 这个联合索引的。对于单个的a 列查询SELECT * FROM TABLE WHERE a=xxx,也可以使用这个( a, b ) 索引。但对于b 列的查询SELECT • FROM TABLE WHERE b=xxx ,则不可以使用这棵B+ 树索引。可以发现叶子节点上的b 值为1 、2、1 、4 、1 、2. 显然不是排序的,因此对于b 列的查询使用不到(a, b) 的索引。
联合索引的第二个好处是已经对第二个键值进行了排序处理。例如,在很多情况下应用程序都需要查询某个用户的购物情况,并按照时间进行排序,最后取出最近三次的购买记录,这时使用联合索引可以避免多一次的排序操作,因为索引本身在叶子节点已经排序了。
对于索引的选择,如果有排序,联合索引里有排序列,则优化器会选择联合索引,省去排序,但是要看B+树上对那个列已经进行排序了
InnoDB 存储引擎支持覆盖索引(covering index ,或称索引覆盖),即从辅助索引中就可以得到查询的记录,而不需要查询聚集索引中的记录。使用覆盖索引的一个好处是辅助索引不包含整行记录的所有信息,故其大小要远小于聚集索引,因此可以减少大量的IO操作。
对于InnoDB 存储引擎的辅助索引而言,由于其包含了主键信息,因此其叶子节点存放的数据为(primary key 1, primary key2,…, key 1, key2 ,…〉。覆盖索引的另一个好处是对某些统计问题而言的,InnoDB 存储引擎并不会选择通过查询聚集索引来进行统计。由于 表上还有辅助索引,而辅助索引远小于聚集索引,选择辅助索引可以减少IO 操作。
在某些情况下,当执行EXPLAIN 命令进行SQL 语句的分析时,会发现优化器并没有选择索引去查找数据,而是通过扫描聚集索引,也就是直接进行全表的扫描来得到数据。这种情况多发生于范围查找、JOIN 链接操作等情况下。
用户要选取的数据是整行信息,而 索引不能覆盖到我们要查询的信息,因此在对 索引查询到指定数据后,还需要一次书签访问来查找整行数据的信息。虽然 索引中数据是顺序存放的,但是再一次进行书签查找的数据则是无序的,因此变为了磁盘上的离散读操作。如果要求访问的数据量很小,则优化器还是会选择辅助索引,但是当访问的数据占整个表中数据的蛮大一部分时〈一般是20% 左右),优化器会选择通过聚集索引来查找数据。因为之前已经提到过,顺序读要远远快于离散读。
因此对于不能进行索引覆盖的情况,优化器选择辅助索引的情况是,通过辅助索引查找的数据是少量的。这是由当前传统机械硬盘的特性所决定的,即利用顺序读来替换随机读的查找。
MySQL 数据库支持索引提示CINDEX HINT) , 显式地告诉优化器使用哪个索引。
Multi-Range Read 优化的目的就是为了减少磁盘的随机访问,并且将随机访问转化为较为顺序的数据访问,这对
于IO-bound 类型的SQL 查询语句可带来性能极大的提升。Multi-Range Read 优化可适用于range , ref, eq_ref 类型的查询。
当进行索引查询时,首先根据索引来查找记录,然后再根据WHERE 条件来过滤、记录。在支持Index Condition Pushdown 后, MySQL 数据库会在取出索引的同时,判断是否可以进行WHERE 条件的过滤,也就是将WHERE 的部分过滤操作放在了存储引擎层。在某些查询下,可以大大减少上层SQL 层对记录的索取(fetch) ,从而提高数据库的整体性能。
哈希表( Hash Table) 也称散列表,由直接寻址表改进而来。我们先来看直接寻址表。当关键字的全域U 比较小时,直接寻址是一种简单而有效的技术
InnoDB 存储引擎使用哈希算法来对字典进行查找, 其冲突机制采用链表方式,哈希函数采用除法方式。对于缓冲池页的时表来说,在缓冲池中的page页都有一个chain 指针,它指向相同哈希函数值的页。
InnoDB 存储引擎的表空间都有一个space_id ,用户所要查询的应该是某个表空间的某个连续16KB 的页,即偏移量offset。InnoDB 存储引擎将space_id 左移20 位,然后加上这个space_id 和offset,即关键字K space id<<20+space _id+offset ,然后通过除法散列到各个槽中去。
自适应哈希索引采用之前讨论的哈希表的方式实现。不同的是,这仅是数据库自身创建并使用的, DBA 本身并不能对其进行干预。自适应哈希索引经哈希函数映射到一个哈希表中,因此对于字典类型的查找非常快速
全文检索(Full-Text Search) 是将存储于数据库中的整本书或整篇文章中的任意内容信息查找出来的技术。它可以根据需要获得全文中有关章、节、段、句、词等信息,也可以进行各种统计和分析。
全文检索通常使用倒排索引(inverted index) 来实现。倒排索引同B+ 树索引一样,也是一种索引结构。它在辅助表(auxiliary table) 中存储了单词与单词自身在一个或多个文档中所在位置之间的映射。这通常利用关联数组实现,其拥有两种表现形式。
InnoDB 存储引擎采用 full inverted index的方式。在InnoDB 存储引擎中,将(DocumentId , Position) 视为一个" ilist"。因此在全文检索的表中,有两个列,一个是word 字段,另一个是ilist 字段,并且在word 字段上有设有索引。此外,由于InnoDB 存储引擎在ilist 字段中存放了Position 信息,故可以进行Proximity Search。
正如之前所说的那样,倒排索引需要将word 存放到一张表中,这个表称为Auxiliary Table (辅助表)。在InnoDB 存储引擎中,为了提高全文检索的并行性能,共有6 张Auxiliary Table ,目前每张表根据word 的Latin 编码进行分区。
Auxiliary Table 是持久的表,存放于磁盘上。然而在InnoDB 存储引擎的全文索引中,还有另外一个重要的概念FTS Index Cache (全文检索索引缓存),其用来提高全文检索的性能。
FTS Index Cache 是一个红黑树结构,其根据(word , ilist) 进行排序。这意味着插入的数据已经更新了对应的表,但是对全文索引的更新可能在分词操作
用的少,略