SQL 调优 [ 1 ]

1. 本节目标

了解使用主键查询与不使用主键查询在性能上的区别

掌握如何查看EXPLAIN执行计划

掌握执行计划结果中各字段的含义

掌握执行计划结果中type列所各指标的含义

掌握执行计划结果中Extra列所各指标的含义

掌握如何通过执行计划的结果进行 SQL 调优

掌握发生索引覆盖的原因及对性能的影响

掌握发生回表查询的原因及对性能的影响

了解 MySQL 内部对不同SELECT查询场景进行优化的方式,包括:

  • WHERE子句优化

  • 范围查询优化

  • 索引合并优化

  • 索引下推优化

  • IS NULL优化

  • ORDER BY优化

  • GROUP BY优化

  • DISTINCT优化

  • 函数调用优化

掌握索引失效的场景

掌握在实际数据操作在使用索引的原则

2. 本节我们可以解决的问题

  1. 说一下你了解的关于数据库优化要考虑哪几个层面的因素?
  2. 介绍一下什么是索引?
  3. 索引的作用是什么?
  4. 索引用到了哪些数据结构?
  5. 索引是如何升查询效率的?
  6. 什么时候应该创建索引?
  7. 在哪些列上创建索引?
  8. 索引越多越好吗?为什么?
  9. 如何查看索引是否生效?
  10. 知道执行计划 (Explain) 吗?它的作用是什么?
  11. 执行计划结果中各列的含义了解吗?
  12. 执行计划的type列的含义是什么?包含哪些内容?说说你熟悉的?
  13. 如果type开中显示const意味什么?
  14. 如果Extra列中显示using index意味什么?
  15. 谈谈如何使用EXPLAIN命令来分析查询执行计划?并举例说明如何根据执行计划进行优化?
  16. 什么是索引覆盖?
  17. 什么是回表查询?
  18. 如何避免全表扫描?
  19. 知道索引合并 (优化) 吗?
  20. 什么是索引下推?
  21. 说一下索引失效的场景?
  22. 什么是最左匹配原则?
  23. 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工具完成压测。

mysqlslapMySQL自带的压测工具,无需额外下载安装,随MySQL服务安装包自带。核心功能是模拟多个客户端并发访问数据库、批量执行指定SQL语句,并自动统计平均耗时、最大耗时、最小耗时等性能数据,非常适合做SQL性能对比测试。

核心参数详解

  • -u / -p:数据库用户名、密码,和常规MySQL登录参数一致;

  • --concurrency并发客户端数,模拟同时访问数据库的客户端数量;

  • --iterations单客户端查询次数,每个模拟客户端执行SQL的总次数;

  • --create-schema :指定需要测试的目标数据库名称

  • --engine :指定数据库存储引擎,常规填写InnoDB即可;

  • --number-of-queries:全局最大查询次数限制,总查询数(并发数×单客户端次数)超出该值时,会被强制限制为该阈值;

  • --query :核心参数,双引号内填写需要压测的目标SQL语句

⚠️ 前置要求:使用该工具必须提前配置MySQL环境变量,确保命令行可以直接识别并运行mysqlmysqlslap指令。

主键索引并发压测(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 优化。报告中会清晰展示这条语句使用了哪一个索引、索引对应字段;如果完全没有使用索引,也会明确标识出来。

在执行SELECTDELETEINSERTREPLACE,和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)

两份报告前四列idselect_typetablepartitions完全一致,从第五列type开始,possible_keyskeykey_lenrefrowsfilteredExtra全部出现明显区别,这就是走索引全表扫描两种场景的核心区分点。

  • 主键查询报告中,possible_keyskey字段显示PRIMARY,代表成功使用主键索引;

  • sn无索引查询报告中,possible_keyskey均为NULL,代表完全没有可用索引。

下面我们逐行拆解执行计划返回表中每一列的含义,先建立整体认知,后续结合案例讲解如何依靠这些字段做优化。

4.5.2 执行计划字段说明

列名 说明
id SELECT标识符
select_type SELECT类型
table 查询的表
partitions 查询的分区
type JOIN类型
possible_keys 可能选择的索引
key 实际选择的索引
key_len 索引长度
ref 与索引比较的列
rows 估算要检查的行数
filtered 按条件筛选行的百分比
Extra 附加信息

1. id列:查询标识符

idSELECT语句的序号标识,代表当前分析语句内包含多少条独立查询:

  • 单条简单查询:仅存在 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表与student1UNION合并查询,执行EXPLAIN后报告生成三行记录:

sql 复制代码
EXPLAIN
SELECT id,name FROM student
UNION
SELECT id,name FROM student1;

id=1:第一张表的基础查询;

id=2:第二张表的基础查询;

第三行table字段为<union1,2>:代表将id=1id=2两条查询的结果集做合并,视为一次独立合并操作。 三条记录对应三次独立查询逻辑,由此就能理解id列代表语句内独立查询的总次数。

2. select_type列:查询类型

标识当前这条独立查询的分类,高频取值如下:

  1. SIMPLE :简单查询,无UNION、无嵌套子查询的单条SELECT
  2. PRIMARY :最外层主查询,搭配子查询、UNION出现;
  3. SUBQUERYWHERE中嵌套的内层子查询;
  4. UNIONUNION关键字后第二条及之后的查询;
  5. UNION RESULTUNION合并后的临时结果集;
  6. 增删改语句(INSERT/UPDATE/DELETE)会匹配对应专属类型。

SQL 优化核心聚焦各类查询语句,因此以上查询相关类型需要重点掌握。

3. table列:查询数据表

显示当前查询读取数据的表名,特殊场景标识规则:

  • 普通单表查询:直接展示表名;
  • UNION合并查询:<union a,b>ab代表对应查询的id值,代表合并id=aid=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列:索引占用字节长度

代表本次查询使用索引的字节长度,数值由索引字段的数据类型决定:

  • 主键idBIGINT类型,固定占用 8 字节,对应key_len=8

  • VARCHAR字符串类型,长度由建表时指定的字符长度、字符集共同决定;

  • keyNULL,本列同步为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; 优化时可以结合rowsfiltered两个字段综合评估过滤损耗。

12. Extra列:附加执行信息

补充展示额外执行行为,例如无索引查询示例中显示Using where,代表执行阶段需要通过WHERE过滤全表数据,该字段与type列为两大核心优化字段,后续单独小节完整讲解。

以上就是执行计划返回结果中全部字段的基础定义,下一小节我们会构造更多测试案例,演示子查询、联合查询等不同场景下typeExtra列的各类取值,夯实基础后,正式落地实操 SQL 索引优化。

相关推荐
Databend1 小时前
从传统分区到微分区,Snowflake 与 Databend 如何减少数据扫描
大数据·数据库·sql
ruleslol1 小时前
Redis 雪崩与服务降级
数据库·redis
易番番ERP2 小时前
品牌代理商SKU繁多,ERP如何高效处理新旧规格替换?
数据库·微服务·云原生·sku·易番番erp
leo_yu_yty2 小时前
Mysql 面试准备
数据库·mysql·面试
不会叫的狼2 小时前
sql基础
sql
SelectDB2 小时前
拉卡拉统一金融 OLAP:基于 Apache Doris / SelectDB 实现查询提速 15 倍、资源直降 52%
数据库
SelectDB2 小时前
四川航空湖仓一体:基于 SelectDB / Apache Doris 的多源数据联邦分析实践
数据库
SelectDB3 小时前
网易云音乐日志平台:基于 Apache Doris / SelectDB 替换 ClickHouse 承载日增万亿日志
数据库
herinspace3 小时前
管家婆财贸ERP如何进行基本信息批量搬移
服务器·数据库·管家婆软件·财务软件