Mysql索引学习

前言

最近系统学习了一下mysql的索引知识,感觉收获颇丰,解决了以前的某些疑问,也增加了新的知识。
mysql查询过程
在这里插入图片描述
mysql的索引不是服务器层的内容,而是引擎层实现的,所以每个引擎对索引的实现逻辑是不一样的。
mysql的引擎大致分为三类:
• 官方引擎,如MyISAM,Innodb;
• 社区引擎
• 第三方引擎

索引的类型

• B-TREE索引
• 哈希索引
• R-TREE空间数据索引
• 全文索引
• 聚簇索引
• 覆盖索引
• 其它(不介绍)

B-TREE索引

PS:下面的内容需要了解链表和二叉树知识
简单描述一下:
二叉查找数,左节点小于根节点,根节点小于由节点
平衡二叉树,左右高度最大差一
平衡多路查找二叉树(AVL树),左节点小于根节点,根节点小于由节点,左右高度最大差一
一般的B-TREE索引
B-TREE索引是一个术语,不是指具体某种结构的索引。比如Innodb使用的是B+Tree结构,NDB集群使用的是T+TREE结构,但是都叫B-TREE索引。
不同的结构的B-TREE索引性能不同,优缺点也不同,不限定结构来说优缺点都是不严谨的。
一般来讲,B-TREE是一个平衡多路查找二叉树,数据结构如下:
在这里插入图片描述
每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。
模拟查找,查找key=13的数据:

  1. 查找根节点,读取磁盘1根节点的数据,key只为17,13小于17,在左子节点;
  2. 读取磁盘二左子节点的数据,key值分别为10、16,13大于10,小于16,所以在P4指定的磁盘块中;
  3. 读取磁盘块5的数据,数据是个有顺链表,从链表中查找key等于13的data,返回数据;
    整个查找过程需要三次IO操作,三次内存查找操作,最后链表是有序的,可以用二分法查找。三次IO操作是查询效率的决定因素。
    PS:如果只查找key=17,实际只有1次IO,1次内存查找。

Innodb的B+TREE索引

数据结构如下:
在这里插入图片描述
模拟查找,查找key=13的数据:

  1. 查找根节点,读取磁盘1根节点的数据,key只为17,13小于17,在左子节点;
  2. 读取磁盘二左子节点的数据,key值分别为10、16,13大于10,小于16,所以在P4指定的磁盘块中;
  3. 读取磁盘块5的数据,数据是个有顺链表,从链表中查找key等于13的data,返回数据;
    整个查找过程需要三次IO操作,三次内存查找操作,最后链表是有序的,可以用二分法查找。三次IO操作是查询效率的决定因素。
    PS:如果只查找key=17,实际也是三次IO,三次内存查找。
    PS:data中只保存了索引列的值;
    B-TREE和B+TREE对比:
    B-TREE优点:
    1、特定key查询,B-TREE比B+TREE快,IO次数少;
    B-TREE缺点:
    1、数据保存在节点中,data较大时,可能会导致节点变大,磁盘块无法存储一个节点,导致一个节点分裂成多个磁盘块;
    2、查询性能不稳定,有的快,有的慢,不好优化;
    B+TREE:
    1、查询性能稳定;
    2、节点不保存data,所以能存储更多的key;
    PS:
    Innodb一个节点为一个数据页,每个数据页大小为16k,所以一个三阶B+TREE,key为bigint 8个字节的话,指针按8字节算,一个数据页,大致可以存储16K/(8 + 8)= 1000(1KB 按1000算) ,所以一个数据页大致可以存储 = 10亿条数据;
    索引适用的查询
    注意:前提条件是不满足覆盖索引
    注意:前提条件是不满足覆盖索引
    注意:前提条件是不满足覆盖索引
    重要的事情说三遍!!!!!!

    测试表:
CREATE TABLE `user` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `name` char(10) NOT NULL,
  `nick_name` varchar(20) NOT NULL DEFAULT '' COMMENT '昵称',
  `job` varchar(20) NOT NULL DEFAULT '' COMMENT '职业',
  `age` varchar(20) NOT NULL DEFAULT '' COMMENT '年龄',
  PRIMARY KEY (`id`),
  KEY `index_name` (`name`,`nick_name`,`job`)
) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

索引为:name、nick_name,job
1、首列全值匹配:

explain select * from user where name = 'zh';
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ref",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "40",
    "ref": "const",
    "rows": 1,
    "filtered": 100,
    "Extra": "Using where; Using index"
  }
]

使用了索引
2、非首列全值匹配

explain select * from user where nick_name = 'ligoudan';
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ALL",
    "possible_keys": null,
    "key": null,
    "key_len": null,
    "ref": null,
    "rows": 3,
    "filtered": 33.33,
    "Extra": "Using where"
  }
]

不使用索引
3、where 条件全字段,顺序和索引一致

explain select * from user where name = 'zh' and nick_name = 'zhanggoudan';
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ref",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "122",
    "ref": "const,const",
    "rows": 1,
    "filtered": 100,
    "Extra": "Using where; Using index"
  }
]

索引正常使用
4、where 条件全字段,顺序和索引不一致

explain select * from user where nick_name = 'zhanggoudan'  and name = 'zh';
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ref",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "122",
    "ref": "const,const",
    "rows": 1,
    "filtered": 100,
    "Extra": "Using where; Using index"
  }
]

索引正常使用,所以最左匹配原则说的并不是字段的顺序。
5、全字段,首列在where,其它在order by

explain select * from user where name = 'zh' order by  nick_name;
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ref",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "40",
    "ref": "const",
    "rows": 1,
    "filtered": 100,
    "Extra": "Using where; Using index"
  }
]

索引正常使用
6、全字段,首列在order by,其它在where

explain select * from user where nick_name = 'zhanggoudan'  order by  name;
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ALL",
    "possible_keys": null,
    "key": null,
    "key_len": null,
    "ref": null,
    "rows": 3,
    "filtered": 33.33,
    "Extra": "Using where; Using filesort"
  }
]

不使用索引
7、首列模糊匹配

explain select * from user where name like '%zh%' ;
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "index",
    "possible_keys": null,
    "key": "index_name",
    "key_len": "122",
    "ref": null,
    "rows": 3,
    "filtered": 33.33,
    "Extra": "Using where; Using index"
  }
]

没有使用索引
8、首列前缀匹配

explain select * from user where name like 'zh%' ;
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "range",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "40",
    "ref": null,
    "rows": 1,
    "filtered": 100,
    "Extra": "Using where; Using index"
  }
]

可以使用索引
9、范围查询

explain select * from user where name >= 'allo' and name <= 'bill';
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "range",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "40",
    "ref": null,
    "rows": 1,
    "filtered": 100,
    "Extra": "Using index condition"
  }
]

使用了索引,但是使用的是Using index condition,mysql5.6之后的特性
11、多列带模糊查询

explain select * from user where name = 'zh' and nick_name = 'lig%' and job = '123' ;
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ref",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "204",
    "ref": "const,const,const",
    "rows": 1,
    "filtered": 100,
    "Extra": "Using index condition"
  }
]

使用了索引,但是使用的是Using index condition,mysql5.6之后的特性
10、只有order by
一般无法使用索引

哈希索引

哈希索引,只能精确匹配,因为存储结构就是哈希表。
数据结构:
在这里插入图片描述
查询场景:
Mysql中只有memory引擎支持哈希索引,所以一般我们永不上。
优点:
1、查询性能很好,根绝哈希值直接定位数据;
缺点:
1、无法排序,哈希不支持排序;
2、必须精确查询才能使用索引;
3、哈希冲突问题;

空间索引R-TREE

mysql的MYISAM支持空间索引,用不到,不需要了解。
全文索引FULLTEXT
我暂时不了解这部分内容。

前缀索引

有时候数据量比较大且索引子单比较长,这时候可以建前缀索引来减小索引空间的开销。但是这样会减小索引的可选择度。
可选择度指,补充的值和总记录数的比值。
所以使用前缀索引的时候,要先确认一个比较合理的前缀。
如何选择一个合适的长度,以user为例:
1、先计算列的可选择性,以name列为例,

select count(distinct (name)) / count(*) from user;

结果是:0.2308
所以只要选择前缀越接近约好;
2、计算前缀列的可选择性

select count(distinct (left(name, 1))) / count(*) from user;
select count(distinct (left(name, 2))) / count(*) from user;

结果是:0.2308、0.2308(数据造的有问题)
按照这个结果来看,只用长度为1的前缀就能达到目标了。
因为数据造的有问题,数据更多的时候就能看出不同的长度有明显的差别了。

多列索引

一般也叫联合索引或者复合索引,上面的例子已经说明这个索引的情况了。
一般where条件或者order by 涉及多个列的时候,就需要多列索引。

聚簇索引

聚簇索引不是一种单独的索引类型,而是一种数据存储方式。
在InnoDB中,索引可以分为两类,聚簇索引、辅助索引(或者叫二级索引);
一般的多列索引或单列索引都是辅助索引,而innnodb会根据主键创建一个聚簇索引。
在InnoDB中,聚簇索引保存了B-TREE和数据行,在叶子节点中包含了完整的行数据。
数据结构就是上面B+Tree的数据结构。

因此聚簇索引和辅助索引之间产生了一个问题:
使用辅助索引查询,需要回表查询不在索引列的数据,聚簇索引不需要,因此辅助索引会产生额外的IO操作。
什么是回表查询,如下图所示:
在这里插入图片描述

PS:InnoDB中没有非聚簇索引,有二级索引,myisam引擎中有非聚簇索引。
聚簇索引的优缺点:
优点

  1. 聚簇索引获取数据较为简单,可以从将B-Tree中获取
  2. 聚簇索引支持范围查询,
    缺点
  3. 聚簇索引的更新成本比较高,可能会导致“页分裂”。
  4. 插入速度严重依赖于插入顺序,按照主键进行插入的速度是加载数据到Innodb中的最快方式。如果不是按照主键插入,最好在加载完成后使用OPTIMIZE TABLE命令重新组织一下表。
  5. 聚簇索引在插入新行和更新主键时,可能导致“页分裂”问题。
  6. 聚簇索引可能导致全表扫描速度变慢,因为可能需要加载物理上相隔较远的页到内存中(需要耗时的磁盘寻道操作)。
    另外InnoDB建立聚簇索引是通过唯一非空索引座位聚簇索引,一般也就是主键id。

覆盖索引

覆盖索引不是一种索引类型,可以理解为一种查询工具,如果查询的字段都在索引中,就叫覆盖索引。
在满足覆盖索引的情况下,一般不能使用索引的查询方式,也能使用索引了,比如:

explain select job from user where job like '%ligoudan%';

分析结果:

[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "index",
    "possible_keys": null,
    "key": "index_name",
    "key_len": "204",
    "ref": null,
    "rows": 13,
    "filtered": 11.11,
    "Extra": "Using where; Using index"
  }
]

其它查询场景也是类似的效果,就不一一列举了。
这也是为什么有时候多列索引不符合最左原则的原因。

使用索引优化查询

1、创建单列索引还是多列索引?
如果查询语句中的where、order by、group 涉及多个字段,一般需要创建多列索引,比如:

select * from user where nick_name = 'ligoudan' and job = 'dog';

2、多列索引的顺序如何选择?
一般情况下,把选择性高德字段放在前面,比如:
查询sql:

select * from user where age = '20' and name = 'zh' order by nick_name;

这时候如果建索引的话,首字段应该是age,因为age定位到的数据更少,选择性更高。
但是务必注意一点,满足了某个查询场景就可能导致另外一个查询场景更慢。
3、避免使用范围查询
很多情况下,范围查询都可能导致无法使用索引。
4、尽量避免查询不需要的数据

explain select * from user  where job like '%ligoudan%';
explain select job from user where job like '%ligoudan%';

同样的查询,不同的返回值,第二个就可以使用覆盖索引,第一个只能全表遍历了。
5、查询的数据类型要正确

explain select * from user  where create_date >= now();
explain select * from user  where create_date >= '2020-05-01 00:00:00';

第一条语句就可以使用create_date的索引,第二个就不可以。

总结

系统的了解学习索引相关的知识,最主要的是索引的数据结构,很多优缺点都是数据结构的体现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值