最近系统学习了一下mysql的索引知识,感觉收获颇丰,解决了以前的某些疑问,也增加了新的知识。
mysql查询过程
mysql的索引不是服务器层的内容,而是引擎层实现的,所以每个引擎对索引的实现逻辑是不一样的。
mysql的引擎大致分为三类:
• 官方引擎,如MyISAM,Innodb;
• 社区引擎
• 第三方引擎
• B-TREE索引
• 哈希索引
• R-TREE空间数据索引
• 全文索引
• 聚簇索引
• 覆盖索引
• 其它(不介绍)
PS:下面的内容需要了解链表和二叉树知识
简单描述一下:
二叉查找数,左节点小于根节点,根节点小于由节点
平衡二叉树,左右高度最大差一
平衡多路查找二叉树(AVL树),左节点小于根节点,根节点小于由节点,左右高度最大差一
一般的B-TREE索引
B-TREE索引是一个术语,不是指具体某种结构的索引。比如Innodb使用的是B+Tree结构,NDB集群使用的是T+TREE结构,但是都叫B-TREE索引。
不同的结构的B-TREE索引性能不同,优缺点也不同,不限定结构来说优缺点都是不严谨的。
一般来讲,B-TREE是一个平衡多路查找二叉树,数据结构如下:
每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。
模拟查找,查找key=13的数据:
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、哈希冲突问题;
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引擎中有非聚簇索引。
聚簇索引的优缺点:
优点
覆盖索引不是一种索引类型,可以理解为一种查询工具,如果查询的字段都在索引中,就叫覆盖索引。
在满足覆盖索引的情况下,一般不能使用索引的查询方式,也能使用索引了,比如:
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的索引,第二个就不可以。
系统的了解学习索引相关的知识,最主要的是索引的数据结构,很多优缺点都是数据结构的体现。