目录
六 索引和算法
(一)概述
(二)B+树索引
1 聚集索引
2 非聚集索引
3 B+树索引的分裂
4 B+树索引的管理
(三)B+树索引的使用
1 联合索引
2 覆盖索引
3 优化器选择不使用索引的情况
4 索引提示
5 Multi-Range Read优化
6 Index Condition Pushdown优化
(四)哈希索引
(五)全文索引
1 概述
2 倒排索引
3 InnoDB全文检索
4 全文检索的使用
索引时一种帮助数据库高效查询数据的数据结构,InnoDB支持B+树索引、Hash索引和Full Text索引:
B+树是为磁盘或其他直接存取设备设计的一种平衡查找树。我们知道在InnoDB中,数据文件是按B+树结构组织的,也就是数据文件本身就是索引文件,数据文件存放于磁盘上,那么B+树作为InnoDB的索引结构是非常合适的,为什么使用B-Tree(B+Tree)具体参考:http://blog.codinglabs.org/articles/theory-of-mysql-index.html?tdsourcetag=s_pctim_aiomsg
在InnoDB中,主键列默认是聚集索引,我们假设有如下的表结构:
table example(id, name, age),id为主键,name为人为建立的辅助索引
如下是建立在主键id列的聚集索引:
可以看到,在聚集索引中,叶子节点又称为数据页,按照主键顺序排列,其中保存了完整的数据,由于InnoDB是索引组织表,因此根据对主键的培训查找和范围是非常快的。
注意:由于实际的一个数据页只能按照一棵B+树组织,因此每张表只能有一个聚集索引。
非聚集索引的叶子节点并不包含行记录的完整数据,而是包含相应记录的聚集索引键。非聚集索引不影响数据在聚集索引中的组织,因此每张表上可以有多个辅助索引。如下是建立在name列的非聚集索引:
B+树的索引页的分裂并不总是从页的中间记录开始,InnoDB的Page Header中有以下几个部分来保存插入的顺序信息:
通过这些信息,InnoDB可以决定是向左还是向右进行分裂,如何选取分裂点等。
(1)索引的管理
索引的创建和删除可以通过两种方法:
通过ALTER TABLE语句:
通过Create/Drop INDEX语句:
可以设置对整个列的数据进行索引,也可以指索引一个列的一部分数据,如表t,列b为varchar(8000),先创建一个只索引前100个字符的索引:
创建联合索引:
(2)Fast Index Creation
MySql5.5版本前,索引的添加或删除操作过程为:
当对一张大表进行索引添加和删除操作时,需要很长的时间,从InnoDB1.0.x版本开始支持一种称为Fast Index Creation的索引创建方式。
对于非聚集索引,InnoDB先对表加一个S锁,然后创建索引,不需要新建表,FIC的方式只限定于非聚集索引。
联合索引是指对表上的多个列进行索引,MySQL联合索引是最左优先原则的。最左优先就是说组合索引的第一个字段必须出现在查询组句中,这个索引才会被用到。只要组合索引最左边第一个字段出现在Where中,那么不管后面的字段出现与否或者出现顺序如何,MySQL引擎都会自动调用索引来优化查询效率。根据最左匹配原则可以知道B-Tree建立索引的过程,比如假设有一个3列索引(a, b, c),那么MySQL只会会建立三个索引(a), (a, b), (a, b, c)。
当使用a=xxx或a=xxx AND b=yyy或a=xxx AND b=yyy AND c=zzz或a=xxx AND c=yyy时都会使用索引,但a=xxx AND c=yyy这种情况并不能直接通过联合索引得到结果,因为(a, c)未排序,需要再执行一次filesort排序。
InnoDB支持覆盖索引,即从非聚集索引中就可以查询到的数据,则不会再去查询聚集索引中查询。
另一方面,在进行统计查询时InnoDB通常会选择通过非聚集索引查询,这样可以减少IO操作,例如SELECT COUNT(*) FROM a;
此外,对于联合索引,例如(a, b),一般是不可以直接通过b作为查询条件的,但在统计查询中,b可以作为查询条件,这是优化器会选择这个联合索引进行统计查询。
当进行范围查询时,如下:
SELECT * FROM t WHERE a >1000 AND a < 10000 (a为非聚集索引列)
查询操作通常需要分为两步,首先在列a的索引中查找书签,然后根据书签到聚集索引中查找完整数据。但优化器会对这个查询进行优化,如果查询的数据量较少,则按前面所说的步骤查询,若果查询的数据占整张表的一大部分时(一般是20%左右),优化器会选择通过聚集索引来查找数据。
MySql支持索引提示(INDEX HINT),显式告诉优化器使用哪个索引,通常在以下两种情况时可能需要使用索引提示:
SELECT * FROM t USE INDEX(a) WHERE xxxx
USE INDEX用于向优化器给出建议,但最后的选择还是由优化器决定的。
SELECT * FROM t FORCE INDEX(a) WHERE xxxx
FORCE INDEX用于强制优化器使用的索引。
MySql5.6版本开始支持Multi-Range Read(MRR)优化,目的是为了减少磁盘的随机访问,并将随机访问优化为较为顺序的数据访问。
对于范围查询和JOIN查询操作,MRR的工作方式如下:
MRR优化有以下好处:
可以通过read_rnd_buffer_size参数配置键值缓冲区的大小。
Index Condition Pushdown是MySql5.6开始支持的一种根据索引进行查询的优化方式。
考虑索引查询的过程,首先根据索引来查找记录,然后再通过WHERE条件比对查找出的记录是否符合条件。Index Condition Pushdown会在数据库根据索引查找记录的同时通过WHERE条件进行过滤,提高数据库的整体查询性能。
InnoDB使用哈希算法来实现字典查找,通过链地址法解决冲突,哈希函数采用除法散列的方式。
我们知道除法散列的哈希函数为:h(k) = k mod m
那么InnoDB中是如何确定k和m的呢?
首先,对于innodb_buffer_pool_size=10M,共有640个16KB的页,哈希表需要分配640 * 2= 1280个槽,选取大于等于1280的第一个质数1399作为m。
然后,对于表空间space_id和偏移量offset,k = space_id << 20 + space_id + offset。
InnoDB中的哈希索引是自适应的,不能认为创建。
我们知道B+索引是可以通过前缀进行查找的,例如:
SELECT * FROM t WHERE a like ‘xxx%’;
但通常情况下我们需要检索的是内容包含某个特定次的记录,例如:
SELECT * FROM t WHERE a like ‘%xxx%’;
这种查询通常称为全文检索,从InnoDB1.2.x版本开始,InnoDB支持了全文检索。
全文检索通常使用倒排索引来实现。倒排索引是一种索引数据结构,通过倒排表存储单词与单词位置的映射关系,通常是一个二元组,表现形式有以下两种:
例如,建立一个full inverted index,我们的数据如下:
Message1:We are good
Message2:How are you
建立倒排表如下:
No |
Key |
Location |
1 |
We |
(Message1, 1) |
2 |
are |
(Message1, 2), (Message2, 2) |
3 |
good |
(Message1, 3) |
4 |
How |
(Message2, 1) |
5 |
you |
(Message2, 3) |
InnoDB从1.2.x版本开始支持全文检索,其采用full inverted index方式建立倒排表(辅助表),key为word字段,location为ilist的二元组字段,在InnoDB中,为了提高全文检索的并行性能,共有6张倒排表,每张表根据word的Latin编码进行分区。
倒排表是持久的表,存放于磁盘上,在InnoDB的全文索引中,还有一个Full Text Search Index Cache(全文检索索引缓存),用于提高全文检索性能。
Full Text Search Index Cache是一棵红黑树,根据(word, ilist)进行排序。当插入新数据的事务提交,首先更新FTS Index Cache,然后在进行全文检索时,将Cache中的字段合并到磁盘上的倒排表中。
可以通过innodb_ft_cache_size控制FTS Index Cache的大小,默认为32M,当Cache满时会自动将Cache中的字段刷新到倒排表中。
当删除数据时,InnoDB不会删除倒排表中的记录,而是删除FTS Index Cache中的记录,并将被删除的数据记录在对应的DELETE表中,这样一来,倒排表和DELETE表会越来越大,为此,InnoDB提供了OPTIMIZE TABLE命令,用于手工删除,这个命令还会进行一些其他操作,可以通过innodb_optimize_fulltext_only参数配置只对倒排索引进行操作,可以通过innodb_ft_num_word_optimize来限制每次实际删除的word数量,默认为2000。
MySql数据库通过MATCH()或AGAINST()语法支持全文检索查询,MATCH指定需要被查询的列,AGAINST指定使用何种方法进行查询,下面介绍各种查询模式:
(1)Natural Language
全文检索通过MATCH函数进行查询,默认采用Natural Language模式,表示查询带有指定word的记录,如下:
SELECT * FROM t
WHERE MATCH(content)
AGAINST (‘love’ IN NATURAL LANGUAGE MODE);
由于MATCH默认是Natural Language模式,上述查询还可写为:
SELECT * FROM t
WHERE MATCH(content)
AGAINST (‘love’);
在WHERE条件中使用MATCH函数,查询结果是根据相关性降序排序的,相关性的计算依据以下四个条件:
参数innodb_ft_min_token_size(默认为3)和innodb_ft_max_token_size(默认为84)控制查询字符的长度,当不在这个范围内时,忽略该词的搜索,
(2)Boolean
MySql数据库允许使用IN BOOLEAN MODE修饰符来进行全文检索,当使用该修饰符时,一些字符会有特殊的含义,具体如下:
如:MATCH(body) AGAINST(‘ +Pease -hot‘ IN BOOLEAN MODE)表示必须包含Pease字符串,必须排除hot字符串。
MATCH(body) AGAINST(‘ Pease hot‘ IN BOOLEAN MODE)表示包含Pease或hot。
如:MATCH(body) AGAINST(‘ “Pease pot” @30‘ IN BOOLEAN MODE),表示字符串Pease和pot的间距必须在30字节以内。
MATCH(body) AGAINST(‘ >good 如:MATCH(body) AGAINST(‘ “Pease hot”‘ IN BOOLEAN MODE)表示包含Pease hot。 (3)Query Expansion MySql数据库还支持全文检索的扩展查询,这种查询通常在查询的关键词太短,用户需要隐含知识时进行。比如,用户对单词database的查询,用户可能虚妄查询的不仅仅是包含database的文档,可能还包括那些包含MySql、Oracle、DB2等关键词的文档,我们可以通过在查询短语后添加WITH QUERY EXPANSION或IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION来启用,如: SELECT * FROM t WHERE MATCH(title, body) AGAINST(‘database’ WITH QUERY EXPANSION ) 开开启了扩展查询后,查询分为两个阶段: 特此声明:本系列博客为均为《MySql技术内幕 InnoDb存储引擎》读书笔记,存在错误还请指正 《MySql技术内幕 InnoDb存储引擎》
参考资料