
1. 本节目标
了解使用主键查询与不使用主键查询在性能上的区别
掌握如何查看EXPLAIN执行计划
掌握执行计划结果中各字段的含义
掌握执行计划结果中type列所各指标的含义
掌握执行计划结果中Extra列所各指标的含义
掌握如何通过执行计划的结果进行 SQL 调优
掌握发生索引覆盖的原因及对性能的影响
掌握发生回表查询的原因及对性能的影响
了解 MySQL 内部对不同SELECT查询场景进行优化的方式,包括:
-
WHERE子句优化 -
范围查询优化
-
索引合并优化
-
索引下推优化
-
IS NULL优化 -
ORDER BY优化 -
GROUP BY优化 -
DISTINCT优化 -
函数调用优化
掌握索引失效的场景
掌握在实际数据操作在使用索引的原则
2. 本节我们可以解决的问题
- 说一下你了解的关于数据库优化要考虑哪几个层面的因素?
- 介绍一下什么是索引?
- 索引的作用是什么?
- 索引用到了哪些数据结构?
- 索引是如何升查询效率的?
- 什么时候应该创建索引?
- 在哪些列上创建索引?
- 索引越多越好吗?为什么?
- 如何查看索引是否生效?
- 知道执行计划 (
Explain) 吗?它的作用是什么? - 执行计划结果中各列的含义了解吗?
- 执行计划的
type列的含义是什么?包含哪些内容?说说你熟悉的? - 如果
type开中显示const意味什么? - 如果
Extra列中显示using index意味什么? - 谈谈如何使用
EXPLAIN命令来分析查询执行计划?并举例说明如何根据执行计划进行优化? - 什么是索引覆盖?
- 什么是回表查询?
- 如何避免全表扫描?
- 知道索引合并 (优化) 吗?
- 什么是索引下推?
- 说一下索引失效的场景?
- 什么是最左匹配原则?
select count(*)与select count(1)的区别?
3. 概述
SQL 调优只是完整数据库调优体系中的一个分支,完整数据库调优包含多个优化维度:
-
服务器操作系统层面调优;
-
MySQL软件服务层面调优(各类服务配置参数优化,例如内存分配、IO 相关配置等); -
SQL语句层面调优。
日常开发工作中,我们几乎每天都要编写SQL语句。拿到业务需求后,写出执行高效的SQL是开发的核心能力;同时我们需要一套标准化指标 ,用来判断SQL执行效率、定位性能瓶颈,这也是本篇核心讲解的内容。学习完章全部内容后,我相信大家会对SQL调优建立完整、体系化的认知。
关于数据库级别的优化一般有几个重要的因素:
-
表结构是否正确:比如列是否指定了正确的数据类型。
-
表的类型是否正确:比如对于频繁更新的应用程序通常有很多表,表中有少量的列;对于数据分析的应用程序通常有少量的表,表中有很多列。
-
是否为适应的列建立索引来提升查询效率。
-
是否为每个表选择了适当的存储引擎,并利用了不同存储引擎的优点。
-
每个表是否选择了适当的行格式,比如归档数据可以选用压缩格式来减少空间提升 I/O 效率
-
用于缓存的内存大小是否合适,等等等等......
-
本章节我们主要讨论索引优化的方法。
4. 优化索引
关于索引的基础概念,可以查看:
MySQL 索引 重点 _mysql 唯一 索引-CSDN博客
https://rosetea.blog.csdn.net/article/details/149966414索引的底层数据结构是B+树 ,InnoDB存储引擎默认采用的索引结构就是B+树 。而索引最核心的作用,就是能够有效提升SQL语句的查询效率。
在日常开发中,我们会为业务中频繁查询的列建立索引,以此优化查询性能。但随之而来就衍生出一系列核心问题:
我们该遵循什么原则,利用索引编写高效的查询语句?索引会在哪些场景下失效?如何判断一条SQL是否成功使用了索引?索引生效后,如何评判查询效率、判断是否还有优化空间?以上所有问题,都属于索引优化的核心范畴。
4.1 构建测试数据
在正式讲解索引优化规则之前,我们需要有前置操作:构建百万级测试数据。
sql
-- 修改SQL结束符
delimiter //
-- 创建存储过程
CREATE PROCEDURE p_init_index_data ()
BEGIN
-- 生成学号和主键
DECLARE id BIGINT DEFAULT 100000;
-- 年龄
DECLARE age TINYINT DEFAULT 18;
-- 性别
DECLARE gender BIGINT DEFAULT 1;
-- 班级编号
DECLARE class_id BIGINT DEFAULT 1;
-- 循环计算
DECLARE count INT DEFAULT 0;
-- 创建表
DROP TABLE IF EXISTS index_demo;
CREATE TABLE index_demo (
id bigint auto_increment,
sn varchar(10) NOT NULL,
name varchar(20) NOT NULL,
mail VARCHAR(20),
age TINYINT(1),
gender TINYINT(1),
password VARCHAR(36) NOT NULL,
class_id bigint NOT NULL,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
PRIMARY KEY (id),
index (class_id)
);
-- 插入一条测试数据
INSERT INTO index_demo VALUES (100000, '100000', 'testUser', '100000@qq.com', 18, 1, UUID(), 1, NOW(), NOW());
-- 循环构建数据
WHILE count < 1000000 DO
-- ID和学号
SET id := id + 1;
-- 年龄
IF count % 10 = 0 THEN
SET age := age + 1;
END IF;
IF age > 50 THEN
SET age := 16;
END IF;
-- 性别
IF count % 3 = 0 THEN
SET gender := 0;
ELSE
SET gender := 1;
END IF;
-- 班级编号
SET class_id := class_id + 1;
IF class_id > 10 THEN
SET class_id := 1;
END IF;
-- 写入数据
INSERT INTO index_demo VALUES (id, id, CONCAT('user_',id), CONCAT(id,'@qq.com'), age, gender, UUID(), class_id, NOW(), NOW());
-- 更新count
SET count := count + 1;
END WHILE;
END //
-- 还原SQL结束符
delimiter ;
-- 调用存储过程,开始构建数据,大约20 - 100分钟左右
CALL p_init_index_data();
这段脚本执行耗时较长,大家应该已经提前本地运行完成。接下来我们详细拆解这段存储过程的底层逻辑。
首先,脚本中定义了一系列初始变量,对应数据表中各个字段的初始值,包含自增ID、年龄、性别、班级编号等,同时定义了计数器变量用于循环逻辑判断。
该存储过程会自动创建一张测试数据表,也是我们后续所有索引优化练习的专用表。这张表会批量生成100万条测试数据 ,足够我们直观对比走索引 和不走索引场景下的查询性能差异。
这张测试表包含核心字段如下:自增主键id、学号sn、姓名、邮箱、年龄、性别、密码password、班级编号class_id,以及创建时间create_time、更新时间update_time。
索引配置规则: 为主键id设置主键索引 ,为class_id字段设置普通索引,其余字段均无任何索引,表结构简单清晰,适配我们的索引对比测试需求。
为了保证测试数据规整、可对照,脚本设置了统一的数据拼装规则,所有数据均为程序自动生成:
-
sn学号 :与主键id数值完全一致,仅数据类型为字符串,方便精准对照查询; -
姓名 :固定前缀
test_user_,后缀拼接当前数据的id值,保证姓名唯一; -
邮箱 :前缀拼接
id值,后缀统一为@qq.com,规则统一; -
年龄:取值范围固定在16~50之间,每写入10条数据年龄自增1,达到50后重置为16,循环往复;
-
性别:仅包含0、1两个值,每写入3条数据切换一次性别,模拟真实男女数据分布;
-
password密码 :通过UUID()生成随机字符串,保证每条数据密码唯一; -
class_id班级编号:取值范围1~10,数值自增超过10后重置为1,模拟多班级数据场景; -
时间字段:创建时间、更新时间均取数据写入时的系统当前时间。
整体数据写入逻辑:脚本手动维护自增id,所有字段数值均依托id和循环计数器生成,确保100万条数据规整、不重复,完美适配性能测试场景。
sql
mysql> select count(*) from index_demo;
+----------+
| count(*) |
+----------+
| 1000001 |
+----------+
1 row in set (0.62 sec)
数据初始化完成后,我们基于这张百万级数据表,直观对比主键索引查询 和无索引普通列查询的性能差距。这里我们使用命令行客户端执行查询,能精准展示SQL执行耗时。
4.2 使用主键查询
首先登录MySQL客户端,切换至测试数据库topic01,查询表中指定主键ID的数据。
执行基于主键id的精准查询后可以看到:在100万条数据的大表中,查询耗时显示0.00 sec。
这里的0.00秒并非无耗时,而是代表耗时在10毫秒以内,这个查询性能在大数据量表中是非常优秀的,这就是主键索引的性能优势。
使用主键查询一条记录,观察耗时
sql
mysql> select id, sn, name, mail, age, gender, class_id from index_demo where id = 1020000;
+---------+---------+--------------+----------------+------+--------+----------+
| id | sn | name | mail | age | gender | class_id |
+---------+---------+--------------+----------------+------+--------+----------+
| 1020000 | 1020000 | user_1020000 | 1020000@qq.com | 38 | 1 | 1 |
+---------+---------+--------------+----------------+------+--------+----------+
1 row in set (0.00 sec)
4.3 使用非索引列查询
接下来我们测试无索引列 的查询效果。本次选用sn字段测试,该字段既不是主键,也没有建立任何普通索引,属于纯普通字段。
我们保持查询条件完全一致,仅将where条件从主键id替换为字符串类型的sn,执行相同数值的精准查询。
使用非索引字段查询一条记录,比如使用
sn,观察耗时
sql
mysql> select id, sn, name, mail, age, gender, class_id from index_demo where sn = 1020000;
+---------+---------+--------------+----------------+------+--------+----------+
| id | sn | name | mail | age | gender | class_id |
+---------+---------+--------------+----------------+------+--------+----------+
| 1020000 | 1020000 | user_1020000 | 1020000@qq.com | 38 | 1 | 1 |
+---------+---------+--------------+----------------+------+--------+----------+
1 row in set (1.40 sec)
执行后可以明显看到查询卡顿,最终耗时达到1.40 sec。仅仅单条查询,就消耗了1.40秒以上的时间。
大家可以试想:如果线上服务器面临上万、十万级别的用户并发访问,每一条查询都消耗1秒以上,服务器负载会直接拉满,完全无法支撑业务运行。
由此可见:无索引列的查询效率极低,是线上业务必须优化的场景。
对应的核心优化方案也很明确:如果某个字段在业务中频繁作为查询条件、频繁出现在where子句中,就必须为该字段建立索引,这是最基础、最高效的优化手段。
可以看到使用非索引字段查询同样一条记录的耗时是使用主键列的 60 倍左右
4.4 压测工具
单条查询的性能差距已经非常明显,接下来我们通过并发压测 ,模拟线上多用户同时访问的场景,进一步放大索引与无索引的性能差距。这里使用mysqlslap工具完成压测。
mysqlslap是MySQL自带的压测工具,无需额外下载安装,随MySQL服务安装包自带。核心功能是模拟多个客户端并发访问数据库、批量执行指定SQL语句,并自动统计平均耗时、最大耗时、最小耗时等性能数据,非常适合做SQL性能对比测试。
核心参数详解
-
-u / -p:数据库用户名、密码,和常规MySQL登录参数一致;
-
--concurrency :并发客户端数,模拟同时访问数据库的客户端数量;
-
--iterations :单客户端查询次数,每个模拟客户端执行SQL的总次数;
-
--create-schema :指定需要测试的目标数据库名称;
-
--engine :指定数据库存储引擎,常规填写
InnoDB即可; -
--number-of-queries:全局最大查询次数限制,总查询数(并发数×单客户端次数)超出该值时,会被强制限制为该阈值;
-
--query :核心参数,双引号内填写需要压测的目标SQL语句。
⚠️ 前置要求:使用该工具必须提前配置MySQL环境变量,确保命令行可以直接识别并运行
mysql、mysqlslap指令。
主键索引并发压测(100并发、单客户端100次)
压测配置:模拟100个并发客户端,每个客户端执行100次主键查询,总查询次数10000次。
sql
C:\Users\A1983>mysqlslap -uroot -p123456 --concurrency=100 --iterations=100 --create-schema="topic01" --query="select id,sn,name,mail,age,gender,class_id from topic01.index_demo where id = 1020000;"
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
Average number of seconds to run all queries: 0.194 seconds
Minimum number of seconds to run all queries: 0.062 seconds
Maximum number of seconds to run all queries: 1.110 seconds
Number of clients running queries: 100
Average number of queries per client: 1
压测结果:基于InnoDB引擎执行,10000次查询整体性能优异,平均耗时0.194秒,最小耗时0.062秒,最大耗时1.110秒。100个并发客户端全部执行完成,查询效率完全满足线上业务需求。
非索引列并发压测(30并发、单客户端3次)
由于单条无索引查询耗时就达到1.59秒,高并发场景耗时会指数级增长,因此我们降低压测配置:模拟30个并发客户端,每个客户端仅执行3次普通列查询,总查询次数90次。
sql
C:\Users\A1983>mysqlslap -uroot -p123456 --concurrency=30 --iterations=3 --create-schema="topic01" --query="select id,sn,name,mail,age,gender,class_id from topic01.index_demo where sn = 1020000;"
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
Average number of seconds to run all queries: 9.328 seconds
Minimum number of seconds to run all queries: 8.640 seconds
Maximum number of seconds to run all queries: 9.766 seconds
Number of clients running queries: 30
Average number of queries per client: 1
压测过程中,可通过show processlist;命令查看数据库实时线程,能看到大量客户端同时执行sn字段的查询语句,证明并发压测正常运行。
sql
C:\Users\A1983>mysql -uroot -p123456 -e "SHOW PROCESSLIST;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------+-----------------+-----------------+---------+---------+--------+------------------------+---------------------------------------------------------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-------+-----------------+-----------------+---------+---------+--------+------------------------+---------------------------------------------------------------------------------------+
| 5 | event_scheduler | localhost | NULL | Daemon | 191827 | Waiting on empty queue | NULL |
| 41573 | root | localhost:29700 | NULL | Sleep | 1 | | NULL |
| 41574 | root | localhost:29702 | topic01 | Query | 1 | executing | select id,sn,name,mail,age,gender,class_id from topic01.index_demo where sn = 1020000 |
| 41575 | root | localhost:29703 | topic01 | Query | 1 | executing | select id,sn,name,mail,age,gender,class_id from topic01.index_demo where sn = 1020000 |
| 41576 | root | localhost:29704 | topic01 | Query | 1 | executing | select id,sn,name,mail,age,gender,class_id from topic01.index_demo where sn = 1020000 |
| 41577 | root | localhost:29701 | topic01 | Query | 1 | executing | select id,sn,name,mail,age,gender,class_id from topic01.index_demo where sn = 1020000 |
| 41578 | root | localhost:29705 | topic01 | Query | 1 | executing | select id,sn,name,mail,age,gender,class_id from topic01.index_demo where sn = 1020000 |
| 41579 | root | localhost:29706 | topic01 | Query | 1 | executing | select id,sn,name,mail,age,gender,class_id from topic01.index_demo where sn = 1020000 |
| 41580 | root | localhost:29708 | topic01 | Query | 1 | executing ........
压测结果:仅90次查询,平均耗时就达到9.328秒,最大耗时9.766秒、最小耗时8.640秒,性能极差,完全无法适配线上并发场景。
通过两组对照压测,我们清晰看到索引对SQL性能的决定性影响。但随之而来出现了核心问题:
我们该如何精准优化低效SQL?优化完成后,如何判断优化是否成功?评判优化效果的核心指标是什么?有什么工具可以辅助我们分析SQL性能、定位瓶颈?
以上所有问题,就是我们接下来正式进入SQL索引优化核心阶段要逐一讲解的内容
4.5 执行计划EXPLAIN
我们写完一条SQL语句后,想在正式执行前判断这条语句是否能走索引、能不能有效利用索引,该用什么方式确认? 总不能直接把低效 SQL 丢到数据库里执行,再长时间等待结果,这种排查方式并不科学。 在MySQL 中官方提供了专门的分析手段 ------执行计划 。 通过执行计划,我们可以完整分析当前SQL对索引的使用情况、整体运行逻辑。
这里有一个关键知识点:执行计划仅做语句逻辑分析,不会真正执行这条 SQL ,只会输出分析后的报告结果。我们依靠这份报告,就能针对性完成 SQL 优化。报告中会清晰展示这条语句使用了哪一个索引、索引对应字段;如果完全没有使用索引,也会明确标识出来。
在执行
SELECT,DELETE,INSERT,REPLACE,和UPDATE之前都可以用执行计划分析 SQL 语句的执行情况,以便优化 SQL 语句。
4.5.1 查看执行计划
EXPLAIN使用方式十分简单:直接在完整SQL语句最前方增加EXPLAIN关键字,原查询语句一字不变,仅前置新增关键字即可。
我们结合之前两组测试案例实操演示:主键索引查询、无索引普通列查询,分别加上EXPLAIN查看返回报告。
-
打开命令行客户端,登录
MySQL,切换至测试库topic01; -
主键查询语句末尾加上
\G,可以让结果按列分行展示,可读性更高; -
在语句开头添加
EXPLAIN后复制到客户端执行,得到第一份执行计划报告,后续所有 SQL 优化,都以这份报告内的字段数值作为判断依据,报告能直观告诉我们语句是否走索引、执行效率高低,字段含义后面逐一拆解。
先执行主键索引查询的执行计划,再修改WHERE条件为无索引字段sn,再次执行EXPLAIN拿到第二份报告,将两份报告并排对比,直观区分二者差异。
使用主键查询的执行计划
sql
-- 在查询语句着加入EXPLAIN关键字,查看当前语句的执行计划
mysql> EXPLAIN select id, sn, name, mail, age, gender, class_id from index_demo where id = 1020000\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: index_demo
partitions: NULL
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.01 sec)
使用非索引列查询的执行计划
sql
-- 在查询语句着加入EXPLAIN关键字,查看当前语句的执行计划
mysql> EXPLAIN select id, sn, name, mail, age, gender, class_id from index_demo where sn = '1020000'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: index_demo
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 894343
filtered: 10.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
两份报告前四列id、select_type、table、partitions完全一致,从第五列type开始,possible_keys、key、key_len、ref、rows、filtered、Extra全部出现明显区别,这就是走索引 和全表扫描两种场景的核心区分点。
-
主键查询报告中,
possible_keys与key字段显示PRIMARY,代表成功使用主键索引; -
sn无索引查询报告中,possible_keys与key均为NULL,代表完全没有可用索引。
下面我们逐行拆解执行计划返回表中每一列的含义,先建立整体认知,后续结合案例讲解如何依靠这些字段做优化。
4.5.2 执行计划字段说明
| 列名 | 说明 |
|---|---|
id |
SELECT标识符 |
select_type |
SELECT类型 |
table |
查询的表 |
partitions |
查询的分区 |
type |
JOIN类型 |
possible_keys |
可能选择的索引 |
key |
实际选择的索引 |
key_len |
索引长度 |
ref |
与索引比较的列 |
rows |
估算要检查的行数 |
filtered |
按条件筛选行的百分比 |
Extra |
附加信息 |
1. id列:查询标识符
id是SELECT语句的序号标识,代表当前分析语句内包含多少条独立查询:
-
单条简单查询:仅存在 1 个
id=1; -
包含子查询、
UNION联合查询:语句会拆分为多条独立查询,id数值按解析顺序依次递增。
实操演示:子查询场景
构造嵌套子查询示例:外层查询学生表,WHERE条件匹配内层子查询查出的id,一条语句拆分为两层独立查询。 执行EXPLAIN后报告生成两行记录:
sql
EXPLAIN
SELECT * FROM student
WHERE id IN (SELECT id FROM student WHERE class_id = 102);
-
第一行
id=1:外层主查询,命中主键索引; -
第二行
id=2:内层子查询,识别为子查询类型。 执行顺序优先执行内层子查询,再执行外层查询,id仅做编号区分,不代表执行先后。
sql
mysql> EXPLAIN
-> SELECT * FROM student
-> WHERE id IN (SELECT id FROM student WHERE class_id = 102);
+----+-------------+---------+------------+--------+------------------+----------+---------+--------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+--------+------------------+----------+---------+--------------------+------+----------+-------------+
| 1 | SIMPLE | student | NULL | ref | PRIMARY,class_id | class_id | 9 | const | 1 | 100.00 | Using index |
| 1 | SIMPLE | student | NULL | eq_ref | PRIMARY | PRIMARY | 8 | topic01.student.id | 1 | 100.00 | NULL |
+----+-------------+---------+------------+--------+------------------+----------+---------+--------------------+------+----------+-------------+
2 rows in set, 1 warning (0.02 sec)
实操演示:UNION联合查询场景
构造student表与student1表UNION合并查询,执行EXPLAIN后报告生成三行记录:
sql
EXPLAIN
SELECT id,name FROM student
UNION
SELECT id,name FROM student1;
id=1:第一张表的基础查询;
id=2:第二张表的基础查询;
第三行table字段为<union1,2>:代表将id=1、id=2两条查询的结果集做合并,视为一次独立合并操作。 三条记录对应三次独立查询逻辑,由此就能理解id列代表语句内独立查询的总次数。
2. select_type列:查询类型
标识当前这条独立查询的分类,高频取值如下:
SIMPLE:简单查询,无UNION、无嵌套子查询的单条SELECT;PRIMARY:最外层主查询,搭配子查询、UNION出现;SUBQUERY:WHERE中嵌套的内层子查询;UNION:UNION关键字后第二条及之后的查询;UNION RESULT:UNION合并后的临时结果集;- 增删改语句(
INSERT/UPDATE/DELETE)会匹配对应专属类型。
SQL 优化核心聚焦各类查询语句,因此以上查询相关类型需要重点掌握。
3. table列:查询数据表
显示当前查询读取数据的表名,特殊场景标识规则:
- 普通单表查询:直接展示表名;
UNION合并查询:<union a,b>,a和b代表对应查询的id值,代表合并id=a与id=b的结果集;- 子查询派生表:
<subquery N>,N为内层子查询的id编号。
4. partitions列:查询分区
代表命中的表分区名称,未做分区的普通数据表该字段固定为NULL。 企业生产环境中,我们一般使用中间件做数据分片,很少依赖MySQL原生分区功能,因此本小节不展开讲解。
5. type列:访问类型(优化核心关键字段)
这是判断 SQL 执行效率、指导优化方向最重要的字段,字段取值代表数据读取方式,性能好坏完全依靠该字段判断,后续会单独开辟小节详细讲解所有取值。
6. possible_keys列:候选索引集合
列出当前查询条件下,数据库理论上能够选用的所有索引:
一张表可同时存在单列索引、复合索引,若WHERE字段同时命中多类索引,全部会展示在此列;
字段值为NULL:代表当前查询没有任何可用索引,需要评估是否为WHERE条件字段新增索引;
注意:本列仅为候选集合,列出的索引不代表最终一定会使用。
7. key列:实际使用索引
代表语句真实执行时选中的索引,是优化时重点观察字段:
值为NULL:完全未使用任何索引;
若成功走索引,possible_keys候选集合内必然包含本列展示的索引名;
possible_keys是候选全集,key是最终选中的子集,优化时优先查看key判断索引是否生效。
8. key_len列:索引占用字节长度
代表本次查询使用索引的字节长度,数值由索引字段的数据类型决定:
-
主键
id为BIGINT类型,固定占用 8 字节,对应key_len=8; -
VARCHAR字符串类型,长度由建表时指定的字符长度、字符集共同决定; -
若
key为NULL,本列同步为NULL。
9. ref列:索引匹配对比值
记录和索引字段做等值匹配的常量 / 列,标识索引的匹配来源:
常量匹配(id=1020000):字段显示const,匹配常量时查询效率极高;
关联其他数据表字段:展示关联表字段名;
函数生成值:显示func,代表匹配值由内置函数计算得出; 如需查看具体调用的函数,执行完EXPLAIN后运行SHOW WARNINGS;查看警告日志。
10. rows列:预估扫描行数
数据库预估执行本条语句需要遍历的数据行数,数值越小性能越好:
- 主键精准查询:预估扫描行数为 1,仅匹配一条数据,效率极高;
- 无索引全表扫描:预估扫描行数 98 万 +,需要遍历整张表所有数据,性能极差; 该数值是优化核心参考指标,优化目标就是尽可能降低
rows数值。
11. filtered列:过滤数据百分比
代表预估扫描行数中,经过WHERE条件过滤后保留数据的占比,取值范围 0~100:
数值越大,过滤效率越高;100 代表扫描到的数据全部符合条件,无需过滤;
数值越小,过滤损耗越大,例如无索引查询示例中filtered=10.00,代表 98 万扫描行仅 10% 满足条件,90% 数据会直接丢弃,大量无效扫描损耗性能; 计算公式:有效行数 = rows × filtered ÷ 100; 优化时可以结合rows与filtered两个字段综合评估过滤损耗。
12. Extra列:附加执行信息
补充展示额外执行行为,例如无索引查询示例中显示Using where,代表执行阶段需要通过WHERE过滤全表数据,该字段与type列为两大核心优化字段,后续单独小节完整讲解。
以上就是执行计划返回结果中全部字段的基础定义,下一小节我们会构造更多测试案例,演示子查询、联合查询等不同场景下type、Extra列的各类取值,夯实基础后,正式落地实操 SQL 索引优化。