在日常工作中,经常会遇见一些慢SQL,在分析这些慢SQL时,通常会看下SQL的执行计划,验证SQL执行过程中有没有走索引。之后会调整一些查询条件,增加必要的索引,SQL执行效率就会提升几个数量级。那么为什么加了索引就会能提高SQL的查询效率?为什么有时候加了索引SQL执行?
索引(Index)是帮助 MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查询算法,这种数据结构就是索引。
**索引结构有二叉树索引结构、B树索引结构、B+树索引结构、Hash索引结构、R-Tree(空间索引)、和Full-Text(全文索引)**等等,每一种中都有各自的特点。其中二叉树和B-Tree索引和B+Tree索引类似,但是相比性能要比B+Tree低。在MySQL的InnoDB存储引擎中默认采用的就是B+Tree索引。
索引结构 | 描述 |
---|---|
B+Tree | 最常见的索引类型,大部分引擎都支持B+树索引 |
Hash | 底层数据结构是用哈希表实现,只有精确匹配索引列的查询才有效,不支持范围查询 |
R-Tree(空间索引) | 空间索引是 MyISAM 引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少 |
Full-Text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式,类似于 Lucene, Solr, ES |
索引 | INNODB | MYISAM | MEMORY |
---|---|---|---|
B+Tree索引 | 支持 | 支持 | 支持 |
Hash索引 | 会透明自动建立自适应hash索引(不可手动) | 不支持 | 支持 |
R-Tree索引 | 不支持 | 支持 | 不支持 |
Full-text | 5.6版本后支持 | 支持 | 不支持 |
BTree也是B-Tree,是一种多路二叉树。BTree 的数据存在每个节点中,所以每个节点能够保存的索引值很少,所以存储大量数据时,树的层级会很高,这样就导致与磁盘的 IO 交互次数增多,查找数据的效率就变得很低。
特点:
1)叶节点具有相同的深度,叶节点的指针为空;
2)所有索引元素不重复;
3)节点中的数据索引从左到右递增排列。
B+Tree是B-Tree的变种,非叶子节点不存储data,只存储索引(冗余),可以放更多的索引。叶子节点包含所有索引字段,叶子节点用指针连接,提高区间访问的性能。
B+Tree这样存储的的好处是每一次磁盘IO能够读取的节点更多,也就是树的度(Max.Degree)可以设置的更大一些,因为每次磁盘IO读取的磁盘页数是一定的。例如,每次磁盘IO能够读取1页=4kb,那么省去value的情况下同样一页数据能够读取更多的key,这样就大大减少了磁盘的IO次数。此外,B+Tree也是排好序的数据结构,数据库中><或者order by等都可以直接依赖这一特性。
MySQL中对于索引使用的主要数据结构也是B+Tree,目的也是在读取数据时能够减少磁盘IO。
每一个索引B+树结构都会有一个独立的存储区域来存放,并且在需要进行检索时将这个结构加载到内存区域。真实情况是InnoDB引擎会加载索引B+树结构到内存的Buffer Pool区域。
与红黑树相比,B-Tree和B+Tree两种数据结构都更加矮胖,存储相同数量级的索引数据时,层级更低。 B-Tree和B+Tree之间一个很大的不同,是B+Tree的节点上不储存value,只储存key,而叶子节点上储存了所有key-value集合,并且节点之间都是有序的。
哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。 如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决(这就是模仿的HashMap的底层原理)。
通过索引的key进行一次hash计算,就可以快速获取磁盘文件指针,对于指定索引查找文件非常快,但是对于范围查询(BETWEEN AND)没法支持,有时候也会出现Hash冲突的情况。
另外InnoDB具有自适应hash功能,hash索引是存储引擎根据 B+Tree 索引在指定条件下自动构建的。
范围查询相关问题说明:
1)基本语法格式。
BETWEEN AND
需要两个参数,即范围的起始值和终止值。如果字段值在指定的范围内,则这些记录被返回。如果不在指定范围内,则不会被返回。2)时间边界问题。
datetime
类型字段使用between and
时,如果不指明时分秒,则筛选出的数据是2022-02-04 00:00:00
到2022-04-09 00:00:00
区间的数据,这里经常犯错,如2022-04-09 11:11:11
是不会被筛选到的。3)数字边界问题。数字区间为
[20,23]
4)NOT BETWEEN AND 边界问题。
MySQL
当中不包含边界实际处理:age not between 20 and 23 =》age < 20 and age > 23;
二叉树的特点:左边子节点的数据小于父节点数据,右边子节点的数据大于父节点数据。
红黑树是一种二叉平衡树,可以提高查询效率,此时若再查找6节点只需要遍历3次就能找到了。但红黑树也有缺点,当存储大数据量时,树的高度就会变的不可控, 数量越大,树的高度越高,查询的效率将会大大降低。
主要用来查找文本中的关键字,而不是直接与索引中的值相比较。全文索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的参数匹配。
RTree 主要是为了快速进行空间检索。**R树是用来做空间数据存储的树状数据结构,RTree查询很快,但生成比较慢,因为内部需要根据算法建立一棵最适于空间检索的RTree。**例如:给地理位置,矩形和多边形这类多维数据创建索引。仅MYISAM存储引擎支持。
1)B+树索引并不能直接找到行,只是找到行所在的页,通过把整页读入内存,再在内存中查找。
2)索引的B+树高度一般为2-4层,查找记录时最多只需要2-4次IO。(IO次数取决于B+树的高度,影响性能)
磁盘IO与预读。磁盘读取数据靠的是机械运动,每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分,寻道时间指的是磁臂移动到指定磁道所需要的时间;旋转延迟就是我们经常听说的磁盘转速;传输时间指的是从磁盘读出或将数据写入磁盘的时间。那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms(磁盘IO时间 = 寻道 + 磁盘旋转 + 数据传输时间)左右。
B+ 树结构就是为了每次查找数据时把磁盘 IO 次数控制在一个很小的数量级。
(1)单列索引,包括普通索引(index)、唯一索引(unique inex)、主键索引(primary key),一个单列索引只能包含一列属性。
(2)组合索引,一个组合索引包括两个或两个以上的列。
(3)全文索引(fulltext index),检索出多列文本字段上(text类型)包含某些单词的索引
(4)空间索引:空间索引是对空间数据类型的字段建立的索引
**索引又分为聚集索引(clustered index)和非聚集索引(non-clustered index),也叫辅助索引(secondary index)。聚集索引和非聚集索引都是 B+ 树结构。**主键索引是一种聚簇索引,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引。对于辅助索引的子节点存储的是主键,检索的时候通过主键到主键索引中找到对应数据行。
InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分;
**一般建表会用一个自增主键做聚簇索引,没有的话MySQL会默认创建,但是这个主键如果更改代价较高,会同时影响相关的辅助索引。**故建表时要考虑自增ID不能频繁update这点。
MVCC版本控制里面有具体的介绍,**MySQL是RMDB,数据以行存储,一行数据除了我们定义的列,还包含三个隐藏字段,其中就包括一个rowid,如果我们建表没有指定主键,则默认用rowid作为主键。如果设置了主键,rowid的值=主键值。**B+树中,聚集索引的叶子节点存放了行数据。
MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。
InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构。这棵树的叶节点data域保存了完整的数据记录,这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。
**聚簇索引的叶子节点存放的是主键值和数据行,支持覆盖索引;二级索引的叶子节点存放的是主键值或指向数据行的指针。由于叶子节点(数据页)只能按照一颗B+树排序,故一张表只能有一个聚簇索引。**辅助索引的存在不影响聚簇索引中数据的组织,所以一张表可以有多个辅助索引,辅助索引是单独的索引文件。
InnoDB存储引擎支持覆盖索引,即从辅助索引中就可以得到查询的记录,而不需要查询聚集索引中的记录。
优点:
我们知道innodb的节点数据存放在page(一页大小默认16KB),减少辅助索引路由到聚集索引检索,可以减少大量的IO操作。
SQL 检索的列,超出了覆盖索引的列范围,SQL执行引擎,会根据辅助索引的叶子节点中存放的主键索引ID,去主键索引中查找对应行中的数据。
注意:
聚簇索引和二级索引存储在磁盘。覆盖索引只是一种优化手段,实际上是把查询时需要从聚簇索引加载到的数据存放到内存中,这些数据按照主键来存放,在用于辅助索引查询的时候,减少回表操作。
InnoDB聚簇索引如图所示:
InnoDB 辅助索引如图所示:
MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。下图是MyISAM主键索引的原理图:
在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。如果我们在Col2上建立一个辅助索引,则此索引的结构如下图所示:
最左前缀匹配原则和联合索引的索引存储结构和检索方式是有关系的。在组合索引树中,最底层的叶子节点按照第一列a列从左到右递增排列,但是b列和c列是无序的,b列只有在a列值相等的情况下小范围内递增有序,而c列只能在a,b两列相等的情况下小范围内递增有序。
组合索引的最左前缀匹配原则:使用组合索引查询时,mysql会一直向右匹配直至遇到范围查询(>、<、between、like)就停止匹配。
1)独立的列。如果查询中的列不是独立的,MySQL 就不会使用索引
2)前缀索引。就是对文本的前几个字符建立索引,这样建立起来的索引占用空间更小,所以查询更快。
3)覆盖索引/索引覆盖(不需要回表操作)。就是 select 的数据列只用从索引中就能够取得,不必读取数据行,一个索引包含(覆盖)满足查询结果的数据就叫做覆盖索引。判断标准通过使用 explain,可以通过输出的 extra 列来判断,对于一个索引覆盖查询,显示为 using index,MySQL 查询优化器在执行查询前会决定是否有索引覆盖查询。
4)多列索引(复合索引、联合索引)。
5)**最左前缀原则。**在组合索引中有一个重要的概念:引导列在左边。
6)索引下推。 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
like "xxxx%"
是可以用到索引的,like "%xxxx"
则不行(like “%xxx%” 同理)。like 以通配符开头(‘%abc…’)索引失效会变成全表扫描的操作,1)最左前缀匹配原则
2)= 和 in 可以乱序
3)尽量选择区分度高的列作为索引
4)索引列不能参与计算,保持列“干净”
5)尽量的扩展索引,不要新建索引
InnoDB的索引是B+tree结构,数据也是。InnoDB的数据是按照聚簇索引的结构进行存放数据,子节点为数据节点。存放形式为key+value,key为主键,若是没有定义,默认为rowid,rowid超过2的32方之后会被重置为0,故建议表一定要建立主键,因为数据存放是按照主键来生成聚簇索引数据结构。
另外除了除了聚簇索引,其他都是二级索引,而考虑IO性能增加了优化手段覆盖索引,尽量避免回表操作。但是覆盖索引是按照B+tree结构在缓冲区,所以类似空间索引、全文索引是不会存在覆盖索引的应用范围内。
索引只是为了快速查询到数据,所以如果索引没有命中(没有建立索引,或者查询条件不符合索引命中规则等),数据量大的时候会导致全表扫描。当然如果数据量少的时候,就无需建立索引。优化器确定执行方案,若表统计行数据在2千以内,无需使用索引,直接全表扫描,因为这个数据量加载到内存中很快。