SQL治理经验谈:索引覆盖

背景

explain - format

  • id: query sql 的标识id

  • SELECT_TYPE: 查询的类型*(SIMPLE/PRIMARY/SUBQUERY/DERIVED/UNION/UNION RESULT/* DEPENDENT SUBQUERY/DEPENDENT UNION)

  • table: 表名

  • Partitions: 表连接的分区数

  • type: 查询中使用的访问类型(system/const/eq_ref/ref/range/index/ALL),见下表type的字段解析

  • possible_keys

    • 显示可能应用在这张表中的索引,一个或多个。

    • 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

  • key

    • 实际使用的索引,如果为NULL,则没有使用索引.

    • 查询中若使用了覆盖索引,则该索引和查询的select字段重叠

  • key_len: 使用到的索引的长度

  • ref: 显示了查询条件类型(const/field_name/func

  • rows: query查询到的行数量

    • query完成索引命中之后,才会去检查的行总数

    • 估算性能耗时:rows用来表示在SQL执行过程中会被扫描的行数,该数值越大,意味着需要扫描的行数,相应的耗时更长。但是需要注意的是EXPLAIN中输出的rows只是一个估算值,不能完全对其百分之百相信,如EXPLAIN中对LIMITS的支持就比较有限。可以参考文章《MySQL EXPLAIN limits and errors》

    • 这个rows就是mysql认为必须要逐行去检查和判断的记录的条数。

  • Filtered: where子句的过滤条件

  • Extra: query子句执行的附加信息

expalin - type

|-----------------|--------|----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| System Name | Color | Text on Visual Diagram | Tooltip Related Information |
| SYSTEM | Blue | Single row: system constant | Very low cost |
| CONST | Blue | Single row: constant | Very low cost |
| EQ_REF | Green | Unique Key Lookup | Low cost -- The optimizer is able to find an index that it can use to retrieve the required records. It is fast because the index search directly leads to the page with all the row data |
| REF | Green | Non-Unique Key Lookup | Low-medium -- Low if the number of matching rows is small; higher as the number of rows increases |
| FULLTEXT | Yellow | Fulltext Index Search | Specialized FULLTEXT search. Low -- for this specialized search requirement |
| REF_OR_NULL | Green | Key Lookup + Fetch NULL Values | Low-medium -- if the number of matching rows is small; higher as the number of rows increases |
| INDEX_MERGE | Green | Index Merge | Medium -- look for a better index selection in the query to improve performance |
| UNIQUE_SUBQUERY | Orange | Unique Key Lookup into table of subquery | Low -- Used for efficient Subquery processing |
| INDEX_SUBQUERY | Orange | Non-Unique Key Lookup into table of subquery | Low -- Used for efficient Subquery processing |
| RANGE | Orange | Index Range Scan | Medium -- partial index scan |
| INDEX | Red | Full Index Scan | High -- especially for large indexes |
| ALL | Red | Full Table Scan | Very High -- very costly for large tables, but less of an impact for small ones. No usable indexes were found for the table, which forces the optimizer to search every row. This could also mean that the search range is so broad that the index would be useless. |
| UNKNOWN | Black | unknown | Note: This is the default, in case a match cannot be determined |

实践

建表

sql 复制代码
CREATE TABLE `test_like` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '账号',
  `age` int NOT NULL DEFAULT '0' COMMENT '年龄',
  `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '邮箱'
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

测试数据

sql 复制代码
INSERT into test_like 
(id, name, age, email)
values
(1, 'aaa', 111, '1111'),
(2, 'aaa', 111, '1111'),
(3, 'aaa', 111, '1111'),
(4, 'aaa', 111, '1111'),
(5, 'aaa', 111, '1111');

添加单列索引

sql 复制代码
-- 单索引 索引覆盖,通过索引idx_on_name的B+树的页节点,可以直接过滤到
ALTER table test_like add key idx_on_name (`name`);

验证案例

案例一:全列检索 select *

测试结果
  • 通配符在右侧

explain select * from test_like where name like '11%';

单列索引,通配符在右侧,select * 会走索引,type=range检索性能好

  • 通配符在左右两侧

explain select * from test_like where name like '%11%';

单列索引,通配符在左右两侧,select * 不会走索引,type=ALL检索性能差

  • 通配符在左侧

explain select * from test_like where name like '%11';

单列索引,通配符在左侧,select * 不会走索引,type=ALL检索性能差

结果说明

加了单列索引还不够,因为select 列 和 where 条件语句 两个变量,都可能导致sql执行效果走了全表扫描,性能非常差。想要性能过得去,得确保通配符在右侧。

案例二:索引列检索 select [索引列]

测试结果
  • 通配符在右侧

单列索引,通配符在右侧,select [索引列] 会走索引,type=range检索性能好

  • 通配符在左右两侧

explain select * from test_like where name like '%11%';

单列索引,通配符在右侧,select [索引列] 会走索引,

type=index,检索性能和二级索引树的数据量相关;sql性能随着二级索引树节点数量变多,性能变差

  • 通配符在左侧

explain select * from test_like where name like '%11';

单列索引,通配符在左侧,select [索引列] 会走索引,

type=index,检索性能和二级索引树的数据量相关;sql性能随着二级索引树节点数量变多,性能变差

结果说明

案例一的select * 性能优化,可以通过案例二的方法去优化:

  • select [索引列] 因为索引覆盖,所以会避免了全表扫描的结果,最终性能有提高

案例三:联合索引列检索 select [多索引列]

我们对表的 name、age设置了联合索引

sql 复制代码
-- 联合索引 索引覆盖,通过联合索引idx_on_name_age的页节点,可以直接过滤到
ALTER table test_like add key idx_on_name_age (`name`, `age`);

测试结果

select 满足最左匹配原则 - 单列
  • select [联合索引左侧单列] + 通配符在右侧

explain select name from test_like where name like '11%';

联合索引,通配符在右侧,select [索引列] 会走索引,type=range检索性能好

  • select [联合索引左侧单列] + 通配符在左右两侧

explain select name from test_like where name like '%11%';

联合索引,通配符在两侧,select [索引列] 会走索引,type=index,检索性能随索引数据量变大而变差

  • select [联合索引左侧单列] + 通配符在左侧

explain select name from test_like where name like '%11';

联合索引,通配符在左侧,select [索引列] 会走索引,type=index,检索性能随索引数据量变大而变差

select 不满足最左匹配原则 - 单列
  • select [联合索引非最左侧单列] + 通配符右侧

explain select age from test_like where name like '11%';

联合索引,通配符在右侧,select [索引列] 会走索引,type=range,检索性能好

  • select [联合索引非最左侧单列] + 通配符左右两侧

explain select age from test_like where name like '%11%';

联合索引,通配符在两侧,select [索引列] 会走索引,type=ALL,检索性能差

  • select [联合索引非最左侧单列] + 通配符左侧

explain select age from test_like where name like '%11';

联合索引,通配符在左侧,select [索引列] 会走索引,type=ALL,检索性能差

select 满足最左匹配原则 - 多列索引列(不包含非索引列)
  • select [联合索引多列] + 通配符在右侧

explain select name,age from test_like where name like '11%';

联合索引,通配符在右侧,select [索引列] 会走索引,type=range,检索性能好

  • select [联合索引多列] + 通配符在两侧

explain select name,age from test_like where name like '%11%';

联合索引,通配符在两侧,select [索引列] 会走索引,type=ALL,检索性能差

  • select [联合索引多列] + 通配符在左侧

explain select name,age from test_like where name like '%11';

联合索引,通配符在左侧,select [索引列] 会走索引,type=ALL,检索性能差

select 满足最左匹配原则 - 多列索引列(包含非索引列)
  • select [联合索引多列+非索引列] + 通配符在右侧

explain select name,age,email from test_like where name like '11%';

联合索引,通配符在右侧,select [索引列] 会走索引,type=range,检索性能好

  • select [联合索引多列+非索引列] + 通配符在两侧

explain select name,age,email from test_like where name like '%11%';

联合索引,通配符在两侧,select [索引列] 会走索引,type=ALL,检索性能差

  • select [联合索引多列+非索引列] + 通配符在左侧

explain select name,age,email from test_like where name like '%11';

联合索引,通配符在左侧,select [索引列] 会走索引,type=ALL,检索性能差

结果说明
  • 联合索引下,检索最左侧单列,无论通配符位置,都会索引覆盖

  • 联合索引下,检索非最左侧单列,只有通配符左侧位置,才会索引覆盖

  • 联合索引下,检索多列索引,无论是否包含非索引列,通配符在右侧时,才会索引覆盖

参考文章

EXPLAIN in SQL:https://www.geeksforgeeks.org/explain-in-sql/

官网Mysql的EXPLAIN信息描述:

https://dev.mysql.com/doc/workbench/en/wb-performance-explain.html

其他文章

SQL性能治理经验谈

理解到位:灾备和只读数据库

记录一次Mysql死锁事件(由Insert与uniqueKey导致)

一文带你看懂:亿级大表垂直拆分的工程实践

亿级大表冷热分级的工程实践

相关推荐
林的快手6 分钟前
209.长度最小的子数组
java·数据结构·数据库·python·算法·leetcode
HEU_firejef38 分钟前
Redis——缓存预热+缓存雪崩+缓存击穿+缓存穿透
数据库·redis·缓存
KELLENSHAW1 小时前
MySQL45讲 第三十七讲 什么时候会使用内部临时表?——阅读总结
数据库·mysql
一只淡水鱼662 小时前
【mybatis】详解 # 和 $ 的区别,两者分别适用于哪种场景,使用 $ 不当会造成什么影响
sql·spring·mybatis·sql注入
SelectDB2 小时前
飞轮科技荣获中国电信星海大数据最佳合作伙伴奖!
大数据·数据库·数据分析
小刘鸭!3 小时前
Hbase的特点、特性
大数据·数据库·hbase
凡人的AI工具箱3 小时前
每天40分玩转Django:Django表单集
开发语言·数据库·后端·python·缓存·django
奔跑草-3 小时前
【数据库】SQL应该如何针对数据倾斜问题进行优化
数据库·后端·sql·ubuntu
Elastic 中国社区官方博客3 小时前
如何通过 Kafka 将数据导入 Elasticsearch
大数据·数据库·分布式·elasticsearch·搜索引擎·kafka·全文检索
P.H. Infinity3 小时前
【Redis】配置序列化器
数据库·redis·缓存