一、InnoDB中的索引
InnoDB存储引擎支持以下几种常见的索引:B+树索引、全文索引、哈希索引,其中比较关键的是B+树索引。
1. B+树索引介绍
InnoDB中的索引自然也是按照B+树来组织的,前面我们说过B+树的叶子节点是用来放数据的。但是放什么数据呢?索引肯定是要放的。因为B+树的作用本来就是为了快速检索数据而提出的一种数据结构。索引是怎么放的呢?
数据库中的数据才是我们需要的。索引只是一个辅助数据,甚至于一个表可以没有自定义索引。InnoDB中的数据是如何组织的呢?
1.1 聚集索引 / 聚簇索引
InnoDB中使用了聚集索引,就是将表的主键用来构建一颗B+树,并且将整张表的行记录数据存放在 B+树的叶子节点中。也就是所谓的索引即数据,数据既索引。由于聚集索引是利用表的主键构建的,所以每张表,只能拥有一个聚集索引。
聚集索引的叶子节点就是数据页,换句话说,数据页上存放的是完整的每行记录。因此聚集索引的一个优点是:通过聚集索引能获取完整的整行数据。另一个优点是:对于主键的排序查找和范围查找速度非常的快。
1.1.1 聚集索引 / 聚簇索引 的创建
下面是我们数据表的结构:
其中我们指定了 id 是一个主键(聚集索引)
ps:我们下面操作数据库,都是以这个表进行操作的。
数据表里面的数据
当指定一个主键后,并且在表里面添加数据后,B+树的结构如下:
如果我们在创建数据表的时候,没有定义主键的话。MySql会使用唯一性索引。没有唯一性索引,mysql会创建一个隐含的列RowId来做主键,然后用这个主键来建立聚集索引。
聚集索引只能在搜索条件是主键的时候才能发挥作用,因为B+树中的数据都是按照主键进行排序的。
如果我们想以别的列作为搜索条件怎么办?我们一般会建立多个索引,这些索引被称为辅助索引 / 二级索引。
每建立一个索引,就有一颗B+树,一个索引就是一颗B+树
1.2 辅助索引 / 二级索引
辅助索引(Secondary Index,也称二级索引、非聚集索引) 。
叶子节点并不包含行记录的全部数据。叶子节点除了包含键值以外,每个叶子节点中的索引行中还包含了一个书签(bookmark)。该书签用来告诉InnoDB存储引擎,哪里可以找到与当前索引相对应的行数据。因此InnoDB存储引擎的辅助索引的书签就是相应行数据的聚集索引键(就是主键)
1.2.1 辅助索引 / 二级索引 的创建
我们为username列创建辅助索引
CREATE INDEX user_index_username ON user_info (username);
索引创建的格式
使用CREATE INDEX创建,语法为:
indexName:索引的名称
tableName:表的名称
columnName:列的名称
CREATE INDEX indexName ON tableName (columnName);
这是我们创建的索引:
我们是用 username列创建的辅助索引,那么B+树的叶子节点就是这个样子:
二级索引下面对应的就是当前列的聚集索引。
1.3 回表
辅助索引的存在并不影响数据在聚集索引中的组织。因此每个表上可以有多个辅助索引。当通过辅助索引来寻找数据时,InnoDB存储引擎会遍历辅助索引并通过叶子节点的指针指向主键索引的主键。然后再通过主键索引(聚集索引)来找到一个完整的记录。这个过程也被称为 回表。也就是根据辅助索引的值查询一条完整的用户记录需要使用 2颗B+树(一次辅助索引,一次聚集索引)。
B+树结构图:
为什么我们还需要一次回表操作呢?直接把完整的用户记录放到辅助索引的叶子节点上不就好了吗?
如果把完整的用户记录放到叶子节点是可以不用回表的。数据量小倒还好,如果几百万几千万数据。是不是太浪费磁盘空间了?
每建立一颗B+树都需要把所有的用户记录拷贝一遍,而且每次对数据的变化要在所有包含数据的索引中全部都修改一次,性能也非常低下。
很明显,回表的记录越少,性能提升就越高,需要回表的记录越多,使用二级索引的性能就越低,甚至让某些查询宁愿使用全表扫描也不愿意使用二级索引。
那时候时候采用全表扫描的方式?什么时候使用二级索引+回表的方式去执行呢?这个就是查询优化器的工作了,查询优化器回事先对表中的记录算一些统计数据,然后再利用这些统计数据根据查询的条件来计算一下回表的次数。需要回表的记录越多,就越倾向于使用全表扫描,反之倾向使用二级索引+ 回表的方式。这个我们后面会讲到的。我们先来讲索引。
1.4 联合索引 / 复合索引
前面我们对索引的描述,隐含了一个条件,那就是构建索引的字段只有一个,但实践工作中构建索引的完全可以是多个字段。所以,将表上的多个列组合起来进行索引,我们称之为联合索引(复合索引)。
注意一点,建立联合索引只会建立 一颗B+树
,多个列分别建立索引,则会分别以每个列建立 一颗B+树,有几个列就是有几颗B+树,比如,index(username),index(status) 就是分别对 username、status两个列构建了 2个B+树。
1.4.1联合索引 / 复合索引 的创建
create index user_index_username_status_create on user_info(username,status,create_time)
联合索引创建的格式
使用CREATE INDEX创建,语法为:
indexName:索引的名称
tableName:表的名称
columnName:多个列以逗号隔开,
CREATE INDEX indexName ON tableName (columnName);
B+树的创建规则如下:
- 先把第一个索引进行排序 就是 username
- 在记录username列相同(这里的相同指的是里面的数据)的情况下,采用status进行排序。
这个就是类似B+树组合索引的结构。最后的指针还是指向聚集索引里面的值。
从上面可以看到,他是以第一个列来创建索引的。如果我跳过第一个列去进行查询,会走索引吗?
1.4 覆盖索引
既然多个列可以组合成联合索引,那么辅助索引自然也可以由多个列组成。
InnoDB存储引擎支持覆盖索引(coving index,或称索引覆盖),既从辅助索引中就可以得到查询的记录。而不需要查询聚集索引中的记录(回表),使用覆盖索引的一个好处是辅助索引不包含整行记录的所有信息,故其大小要要远小于聚集索引,因此可以减少大量的IO操作。所以谨记。
覆盖索引并不是索引类型的一种。
其实覆盖索引就是,不要去查询其他列,就查询你联合索引里面的列,或者辅助索引里面的列,不要让他去发生 回表操作。
2. 哈希索引
InnoDB存储引擎除了我们前面所说的各种索引,还有一种自适应哈希索引,我们知道B+树的查找次数,取决于B+树的高度,在生产环境中,B+树的高度一般为3、4层 (B+树的高度只能去估算,3层大约在2000万数据量级别,再往上要考虑分库分表了)。故需要3、4次的IO查询。
所以在InnoDB存储引擎内部自己去监控索引表,如果监控到某个索引经常使用,那么就认为这个索引是个 热数据
,然后内部为这个热数据创建一个hash索引,称为自适应哈希索引(Adaptive Hash Index,AHI),创建以后,如果下次又查询到这个索引,那么直接通过hash算法推导出记录的地址。直接一次就能查到数据。比重复去B+树查询3-4个节点的效率高了不少。
InnoDB存储引擎使用的哈希函数采用除法散列方式,其冲突机制采用链表方式。注意,对于自适应哈希索引仅是数据库自然创建并使用的,我们并不能干预。
查看是否开启自适应哈希索引
show engine innodb status
哈希值只能用来搜索等值的查询,如
SELECT * FROM table WHERE index co=xxx
而对于其他类型,如范围查找,是不能使用哈希索引的。
3. 全文索引
什么是全文检索(Full-Text Search)?它是将存储于数据库中的整本书或整篇文章中的任意内容信息查找出来的技术。它可以根据需要获得全文中有关章、节、段、句、词等信息,也可以进行各种统计和分析。我们比较熟知的Elasticsearch、Solr等就是全文检索引擎,底层都是基于Apache Lucene的。
举个例子,现在我们要保存唐宋诗词,数据库中我们们会怎么设计?诗词表我们可能的设计如下:
朝代 | 作者 | 诗词年代 | 标题 | 诗词全文 |
---|---|---|---|---|
唐 | 李白 | 静夜思 | 床前明月光,疑是地上霜。 举头望明月,低头思故乡。 | |
宋 | 李清照 | 如梦令 | 常记溪亭日暮,沉醉不知归路,兴尽晚回舟,误入藕花深处。争渡,争渡,惊起一滩鸥鹭。 | |
…. | …. | … | …. | ……. |
要根据朝代或者作者寻找诗,都很简单,比如“select 诗词全文 from 诗词表 where作者=‘李白’”,如果数据很多,查询速度很慢,怎么办?我们可以在对应的查询字段上建立索引加速查询。
但是如果我们现在有个需求:要求找到包含“望”字的诗词怎么办?用
“select 诗词全文 from 诗词表 where诗词全文 like‘%望%’”,这个意味着要扫描库中的诗词全文字段,逐条比对,找出所有包含关键词“望”字的记录,。基本上,数据库中一般的SQL优化手段都是用不上的。数量少,大概性能还能接受,如果数据量稍微大点,就完全无法接受了,更何况在互联网这种海量数据的情况下呢?怎么解决这个问题呢,用倒排索引。
倒排索引就是,将文档中包含的关键字全部提取处理,然后再将关键字和文档之间的对应关系保存起来,最后再对关键字本身做索引排序。用户在检索某一个关键字是,先对关键字的索引进行查找,再通过关键字与文档的对应关系找到所在文档。
于是我们可以这么保存
序号 | 关键字 | 蜀道难 | 静夜思 | 春台望 | 鹤冲天 |
---|---|---|---|---|---|
1 | 望 | 有 | 有 | 有 | 有 |
如果查哪个诗词中包含上,怎么办,上述的表格可以继续填入新的记录
序号 | 关键字 | 蜀道难 | 静夜思 | 春台望 | 鹤冲天 |
---|---|---|---|---|---|
1 | 望 | 有 | 有 | 有 | 有 |
2 | 上 | 有 | 有 |
从InnoDB 1.2.x版本开始,InnoDB存储引擎开始支持全文检索,对应的MySQL版本是5.6.x系列。不过MySQL从设计之初就是关系型数据库,存储引擎虽然支持全文检索,整体架构上对全文检索支持并不好而且限制很多,比如每张表只能有一个全文检索的索引,不支持没有单词界定符( delimiter)的语言,如中文、日语、韩语等。
所以MySQL中的全文索引功能比较弱鸡,了解即可。