MySQL InnoDB索引原理和算法

栏目: 数据库 · 发布时间: 5年前

内容简介:也许你经常用在数据库中,如果索引太多,应用程序的性能可能会受到影响;如果索引太少,又会对查询性能产生影响。所以,我们要追求两者的一个平衡点,足够多的索引带来查询性能提高,又不因为索引过多导致修改数据等操作时负载过高。

也许你经常用 MySQL ,也会经常用索引,但是对索引的原理和高级功能却并不知道,我们在这里一起学习下。

InnoDB 存储索引

在数据库中,如果索引太多,应用程序的性能可能会受到影响;如果索引太少,又会对查询性能产生影响。所以,我们要追求两者的一个平衡点,足够多的索引带来查询性能提高,又不因为索引过多导致修改数据等操作时负载过高。

InnoDB 支持 3 种常见索引:

B+ 

我们接下来要详细讲解的就是 B+ 树索引和全文索引。

哈希索引

InnoDB 存储引擎支持的哈希索引是自适应的,会根据表的使用情况自动为表生成哈希索引,不能人为干预是否在一张表中生成哈希索引。这块内容我们先不展开说,后续补充。

B+ 树索引

B+ 树索引是目前关系型数据库系统中查找最为常用和有效的索引,其构造类似于二叉树,根据键值对快速找到数据。 B+(balance+ tree)B(banlance tree 平衡二叉树) 和索引顺序访问方法 (ISAM: Index Sequence Access Method) 演化而来,这几个都是经典的数据结构。而 MyISAM 引擎最初也是参考 ISAM 数据结构设计的。

基础数据结构

想要了解 B+ 树数据结构,我们先了解一些基础的知识。

二分查找法

又称为折半查找法,指的是将数据顺序排列,通过每次和中间值比较,跳跃式查找,每次缩减一半的范围,快速找到目标的算法。其算法复杂度为 log2(n) ,比顺序查找要快上一些。

如图所示,从有序列表中查找 48 ,只需要 3 步:

MySQL InnoDB索引原理和算法

详细的算法可以参考 二分查找算法

二叉查找树

二叉查找树的定义是在一个二叉树中,左子树的值总是小于根键值,根键值总是小于右子树的值。在我们查找时,每次都从根开始查找,根据比较的结果来判断继续查找左子树还是右子树。其查找的方法非常类似于二分查找法。

MySQL InnoDB索引原理和算法

平衡二叉树

二叉查找树的定义非常宽泛,可以任意构造,但是在极端情况下查询的效率和顺序查找一样,如只有左子树的二叉查找树。

MySQL InnoDB索引原理和算法

若想构造一个性能最大的二叉查找树,就需要该树是平衡的,即平衡二叉树(由于其发明者为 G. M. Adelson-VelskyEvgenii Landis ,又被称为 AVL 树)。其定义为必须满足任何节点的两个子树的高度最大差为 1 的二叉查找树。平衡二叉树相对结构较优,而最好的性能需要建立一个最优二叉树,但由于维护该树代价高,因此一般平衡二叉树即可。

平衡二叉树查询速度很快,但在树发生变更时,需要通过一次或多次左旋和右旋来达到树新的平衡。这里不发散讲。

B+

了解了基础的数据结构后,我们来看下 B+ 树的实现,其定义十分复杂,目标是为磁盘或其他直接存取辅助设备设计的一种平衡查找树。在该树中,所有的记录都按键值的大小放在同一层的叶子节点上,各叶子节点之间有指针进行连接(非连续存储),形成一个双向链表。索引节点按照平衡树的方式构造,并存在指针指向具体的叶子节点,进行快速查找。

下面的 B+ 树为数据较少时,此时高度为 2 ,每页固定存放 4 条记录,扇出固定为 5 (图上灰色部分)。叶子节点存放多条数据,是为了降低树的高度,进行快速查找。

MySQL InnoDB索引原理和算法

当我们插入 28、70、95 3 条数据后, B+ 树由于数据满了,需要进行页的拆分。此时高度变为 3 ,每页依然是 4 条记录,双向链表未画出但是依然是存在的,现在可以看出来是一个平衡二叉树的雏形了。

MySQL InnoDB索引原理和算法

InnoDBB+ 树索引

InnoDBB+ 树索引的特点是高扇出性,因此一般树的高度为 2~4 层,这样我们在查找一条记录时只用 I/O 2~4 次。当前机械硬盘每秒至少 100I/O/s ,因此查询时间只需 0.02~0.04s

数据库中的 B+ 树索引分为聚集索引 (clustered index) 和辅助索引 (secondary index) 。它们的区别是叶子节点存放的是否为一整行的完整数据。

聚集索引

聚集索引就是按照每张表的主键(唯一)构造一棵 B+ 树,同时叶子节点存放整行的完整数据,因此将叶子节点称为数据页。由于定义了数据的逻辑顺序,聚集索引也能快速的进行范围类型的查询。

聚集索引的叶子节点按照逻辑顺序连续存储,叶子节点内部物理上连续存储,作为最小单元,叶子节点间通过双向指针连接,物理存储上不连续,逻辑存储上连续。

聚集索引能够针对主键进行快速的 排序 查找和范围查找,由于是双向链表,因此在逆序查找时也非常快。

我们可以通过 explain 命令来分析 MySQL 数据库的执行计划:

# 查看表的定义,可以看到id为主键,name为普通列
mysql> show create table dimensionsConf;
| Table          | Create Table     
| dimensionsConf | CREATE TABLE `dimensionsConf` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `remark` varchar(1024) NOT NULL,
  PRIMARY KEY (`id`),
  FULLTEXT KEY `fullindex_remark` (`remark`)
) ENGINE=InnoDB AUTO_INCREMENT=178 DEFAULT CHARSET=utf8 |
1 row in set (0.00 sec)

# 先测试一个非主键的name属性排序并查找,可以看到没有使用到任何索引,且需要filesort(文件排序),这里的rows为输出行数的预估值
mysql> explain select * from dimensionsConf order by name limit 10\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: dimensionsConf
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 57
        Extra: Using filesort
1 row in set (0.00 sec)

# 再测试主键id的排序并查找,此时使用主键索引,在执行计划中没有了filesort操作,这就是聚集索引带来的优化
mysql> explain select * from dimensionsConf order by id limit 10\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: dimensionsConf
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 10
        Extra: NULL
1 row in set (0.00 sec)

# 再查找根据主键id的范围查找,此时直接根据叶子节点的上层节点就可以快速得到范围,然后读取数据
mysql> explain select * from dimensionsConf where id>10 and id<10000\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: dimensionsConf
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 56
        Extra: Using where
1 row in set (0.00 sec)

辅助索引

辅助索引又称非聚集索引,其叶子节点不包含行记录的全部数据,而是包含一个书签 (bookmark) ,该书签指向对应行数据的聚集索引,告诉 InnoDB 存储引擎去哪里查找具体的行数据。辅助索引与聚集索引的关系就是结构相似、独立存在,但辅助索引查找非索引数据需要依赖于聚集索引来查找。

MySQL InnoDB索引原理和算法

全文索引

我们通过 B+ 树索引可以进行前缀查找,如:

select * from blog where content like 'xxx%';

只要为 content 列添加了 B+ 树索引(聚集索引或辅助索引),就可快速查询。但在更多情况下,我们在博客或搜索引擎中需要查询的是某个单词,而不是某个单词开头,如:

select * from blog where content like '%xxx%';

此时如果使用 B+ 树索引依然是全表扫描,而全文检索 (Full-Text Search) 就是将整本书或文章内任意内容检索出来的技术。

倒排索引

全文索引通常使用倒排索引 (inverted index) 来实现,倒排索引和 B+ 树索引都是一种索引结构,它需要将分词 (word) 存储在一个辅助表 (Auxiliary Table) 中,为了提高全文检索的并行性能,共有 6 张辅助表。辅助表中存储了单词和单词在各行记录中位置的映射关系。它分为两种:

  • inverted file index (倒排文件索引),表现为{单词,单词所在文档 ID }
  • full inverted index (详细倒排索引),表现为{单词,(单词所在文档 ID , 文档中的位置)}

对于这样的一个数据表:

MySQL InnoDB索引原理和算法

倒排文件索引类型的辅助表存储为:

MySQL InnoDB索引原理和算法

详细倒排索引类型的辅助表存储为,占用更多空间,也更好的定位数据,比提供更多的搜索特性:

MySQL InnoDB索引原理和算法

全文检索索引缓存

辅助表是存在与磁盘上的持久化的表,由于磁盘 I/O 比较慢,因此提供 FTS Index Cache (全文检索索引缓存)来提高性能。 FTS Index Cache 是一个红黑树结构,根据 (word, list) 排序,在有数据插入时,索引先更新到缓存中,而后 InnoDB 存储引擎会批量进行更新到辅助表中。

当数据库宕机时,尚未落盘的索引缓存数据会自动读取并存储,配置参数 innodb_ft_cache_size 控制缓存的大小,默认为 32M ,提高该值,可以提高全文检索的性能,但在故障时,需要更久的时间恢复。

在删除数据时, InnoDB 不会删除索引数据,而是保存在 DELETED 辅助表中,因此一段时间后,索引会变得非常大,可以通过 optimize table 命令手动删除无效索引记录。如果需要删除的内容非常多,会影响应用程序的可用性,参数 innodb_ft_num_word_optimize 控制每次删除的分词数量,默认为 2000 ,用户可以调整该参数来控制删除幅度。

全文检索限制

全文检索存在一个黑名单列表 (stopword list) ,该列表中的词不需要进行索引分词,默认共有 36 个,如 the 单词。你可以自行调整:

mysql> select * from information_schema.INNODB_FT_DEFAULT_STOPWORD;
+-------+
| value |
+-------+
| a     |
| about |
| an    |
| are   |
| as    |
| at    |
| be    |
| by    |
| com   |
| de    |
| en    |
| for   |
| from  |
| how   |
| i     |
| in    |
| is    |
| it    |
| la    |
| of    |
| on    |
| or    |
| that  |
| the   |
| this  |
| to    |
| was   |
| what  |
| when  |
| where |
| who   |
| will  |
| with  |
| und   |
| the   |
| www   |
+-------+
36 rows in set (0.00 sec)

其他限制还有:

  • 每张表只能有一个全文检索索引
  • 多列组合的全文检索索引必须使用相同的字符集和字符序,不了解的可以参考 MySQL乱码的原因和设置UTF8数据格式
  • 不支持没有单词界定符 (delimiter) 的语言,如中文、日语、韩语等

全文检索

我们创建一个全文索引:

mysql> create fulltext index fullindex_remark on dimensionsConf(remark);
Query OK, 0 rows affected, 1 warning (0.39 sec)
Records: 0  Duplicates: 0  Warnings: 1

mysql> show warnings;
+---------+------+--------------------------------------------------+
| Level   | Code | Message                                          |
+---------+------+--------------------------------------------------+
| Warning |  124 | InnoDB rebuilding table to add column FTS_DOC_ID |
+---------+------+--------------------------------------------------+
1 row in set (0.00 sec)

全文检索有两种方法:

  • 自然语言 (Natural Language) ,默认方法,可省略: (IN NATURAL LANGUAE MODE)
  • 布尔模式 (Boolean Mode)(IN BOOLEAN MODE)

自然语言还支持一种扩展模式,后面加上: (WITH QUERY EXPANSION)

其语法为 MATCH()...AGAINST()MATCH 指定被查询的列, AGAINST 指定何种方法查询。

自然语言检索

mysql> select remark from dimensionsConf where remark like '%baby%';
+-------------------+
| remark            |
+-------------------+
| a baby like panda |
| a baby like panda |
+-------------------+
2 rows in set (0.00 sec)

mysql> select remark from dimensionsConf where match(remark) against('baby' IN NATURAL LANGUAGE MODE);
+-------------------+
| remark            |
+-------------------+
| a baby like panda |
| a baby like panda |
+-------------------+
2 rows in set (0.00 sec)

# 查看下执行计划,使用了全文索引排序
mysql> explain select * from dimensionsConf where match(remark) against('baby');
+----+-------------+----------------+----------+------------------+------------------+---------+------+------+-------------+
| id | select_type | table          | type     | possible_keys    | key              | key_len | ref  | rows | Extra       |
+----+-------------+----------------+----------+------------------+------------------+---------+------+------+-------------+
|  1 | SIMPLE      | dimensionsConf | fulltext | fullindex_remark | fullindex_remark | 0       | NULL |    1 | Using where |
+----+-------------+----------------+----------+------------------+------------------+---------+------+------+-------------+
1 row in set (0.00 sec)

我们也可以查看各行数据的相关性,是一个非负的浮点数, 0 代表没有相关性:

mysql> select id,remark,match(remark) against('baby') as relevance from dimensionsConf;
+-----+-----------------------+--------------------+
| id  | remark                | relevance          |
+-----+-----------------------+--------------------+
| 106 | c                     |                  0 |
| 111 | 运营商             |                  0 |
| 115 | a baby like panda     | 2.1165735721588135 |
| 116 | a baby like panda     | 2.1165735721588135 |
+-----+-----------------------+--------------------+
4 rows in set (0.01 sec)

布尔模式检索

MySQL 也允许用修饰符来进行全文检索,其中特殊字符会有特殊含义:

  • +:word 必须存在
  • -:word 必须排除
  • (no operator):word 可选,如果出现,相关性更高
  • @distance: 查询的多个单词必须在指定范围之内
  • >: 出现该单词时增加相关性
  • <: 出现该单词时降低相关性
  • ~: 出现该单词时相关性为负
  • *: 以该单词开头的单词
  • ": 表示短语
# 代表必须有a baby短语,不能有man,可以有lik开头的单词,可以有panda,
select remark from dimensionsConf where match(remark) against('+"a baby" -man lik* panda' IN BOOLEAN MODE);

扩展查询

当查询的关键字太短或不够清晰时,需要用隐含知识来进行检索,如 database 关联的 MySQL/DB2 等。但这个我并没太明白怎么使用,后续补充吧。

类似的使用是:

select * from articles where match(title,body) against('database' with query expansion);

参考资料

  1. 二分查找算法: https://juejin.im/post/5c90ed...
  2. MySQL乱码的原因和设置UTF8数据格式: https://segmentfault.com/a/11...
  3. 《MySQL技术内幕 InnoDB存储引擎 第2版》第5章:索引与算法

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

人人都是产品经理

人人都是产品经理

苏杰 / 电子工业出版社 / 2012-6 / 45.00元

本书为《人人都是产品经理》的升级版,是写给“1到3岁的产品经理”的书,适合刚入门的产品经理、产品规划师、需求分析师,以及对做产品感兴趣的学生,用户体验、市场运营、技术部门的朋友们,特别是互联网、软件行业。作为一名“4岁的产品经理”,作者讲述了过去3年的经历与体会,与前辈们的书不同,本书就像你走到作者身边,说“嗨,哥们!晚上有空吃个饭吗,随便聊聊做产品的事吧”,然后作者说“好啊”。 书名叫“......一起来看看 《人人都是产品经理》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具