一、前言

在后端开发与数据库优化工作中,EXPLAIN 是排查慢SQL的“瑞士军刀”——它能直观展示SQL的执行计划,让我们清晰看到MySQL如何解析SQL、如何选择索引、如何执行联表/分组/过滤操作,从而精准定位性能瓶颈。

二、MySQL EXPLAIN 详解

2.1 EXPLAIN 基础用法

EXPLAIN 用于分析SQL语句的执行计划,支持 SELECTDELETEUPDATEINSERT(部分MySQL版本)语句,核心用法有两种:

  1. 基础用法:EXPLAIN + SQL语句,例:
    EXPLAIN SELECT a.id, b.name, COUNT(a.id) AS cnt 
    FROM table_a a 
    LEFT JOIN table_b b ON a.b_id = b.id 
    WHERE a.status = 1 
    GROUP BY a.id, b.name;
    
  2. 扩展用法:EXPLAIN EXTENDED + SQL语句(可获取更详细的执行计划,后续可通过 SHOW WARNINGS 查看优化后的SQL);EXPLAIN FOR CONNECTION + 连接ID(分析正在执行的SQL)。

执行 EXPLAIN 后,会返回12个核心字段(不同MySQL版本略有差异,以8.0版本为准),每个字段对应执行计划的关键信息。

2.2 EXPLAIN 核心字段逐字段解析

1. id:执行顺序标识
  • 含义:表示SQL执行过程中,操作表的顺序(优先级)。
  • 规则:
    • id 相同:执行顺序由上到下(多表联表时,通常是MySQL默认的嵌套循环顺序)。
    • id 不同:id 值越大,优先级越高,先执行(子查询场景下常见,子查询的id会大于主查询)。
    • id 有相同也有不同:先执行id大的分组,同一分组内按从上到下顺序执行。
  • 示例:子查询场景中,SELECT * FROM table_a WHERE b_id IN (SELECT id FROM table_b WHERE status = 1),子查询的id=2,主查询的id=1,先执行子查询。
2. select_type:查询类型
  • 含义:标识当前查询的类型,判断是简单查询还是复杂查询(子查询、联表等),核心取值如下:
    取值 含义
    SIMPLE 简单查询,无子查询、无UNION、无联表(或联表但无复杂嵌套)
    PRIMARY 复杂查询中的主查询(最外层查询),仅在存在子查询/UNION时出现
    SUBQUERY 嵌套在SELECT/WHERE子句中的子查询(非IN后的子查询,不被物化)
    DERIVED 派生表查询,即FROM子句中的子查询(MySQL会将结果存入临时表)
    UNION UNION 中的第二个及之后的查询
    UNION RESULT UNION 查询的结果集(不操作具体表,仅汇总结果)
    DEPENDENT SUBQUERY 依赖外部查询的子查询(IN后的子查询,外部查询每执行一行,子查询执行一次)
  • 关键作用:通过 select_type 快速判断查询是否包含复杂逻辑(如派生表、依赖子查询),这类查询往往容易产生性能问题。
3. table:操作的表
  • 含义:表示当前行对应的操作表,可以是以下形式:
    • 实际表名(如 table_atable_b)。
    • 别名(如SQL中定义的 ab)。
    • 派生表标识(如 derived2,对应id=2的派生查询)。
    • UNION结果标识(如 union1,2,对应id=1和id=2的UNION查询)。
  • 作用:明确当前执行步骤对应的表,方便定位某张表的性能瓶颈。
4. partitions:分区信息
  • 含义:表示查询涉及的表分区(仅对分区表有效,非分区表显示为 NULL)。
  • 作用:排查分区表的查询是否命中了不必要的分区(如未按分区键查询,导致全分区扫描),从而优化分区使用。
5. type:访问类型(核心优化判断字段)
  • 含义:表示MySQL访问表中数据的方式,直接决定查询性能的优劣,性能从优到劣排序如下:
    system > const > eq_ref > ref > ref_or_null > index_merge > range > index > ALL
  • 关键取值详解:
    1. system:最优,表中只有1行数据(系统表),几乎不会出现(仅系统表或临时表中1行数据时触发)。
    2. const:表中数据通过主键/唯一索引等值查询,最多返回1行数据,MySQL会将其作为常量处理(如 SELECT * FROM table_a WHERE id = 1,id是主键)。
    3. eq_ref:多表联表时,被联表通过主键/唯一索引等值匹配,每行主表数据仅匹配被联表1行数据(LEFT JOIN/INNER JOIN 的最优联表方式,如 a.b_id = b.id,b.id是主键)。
    4. ref:通过普通索引等值查询,可能返回多行数据(如 SELECT * FROM table_a WHERE name = 'test',name是普通索引)。
    5. range:索引范围查询,如 ><BETWEENIN(非全索引)、LIKE 'test%'(前缀匹配),仅扫描索引的某一范围,比全索引扫描更优。
    6. index:全索引扫描,MySQL遍历整个索引但不扫描数据行(索引包含查询所需所有字段,即“覆盖索引”场景),比全表扫描快。
    7. ALL:最差,全表扫描(Full Table Scan),MySQL会遍历整个表的数据行,百万级数据下会导致极慢的查询。
  • 优化目标:至少保证 type 达到 range 级别,最好是 refeq_ref,杜绝 ALL(全表扫描)。
6. possible_keys:可能使用的索引
  • 含义:MySQL优化器认为当前查询可能用到的索引列表(仅为“候选”,不一定实际使用)。
  • 作用:如果该字段为 NULL,说明当前查询没有可选索引,大概率会触发全表扫描,需要针对性创建索引。
7. key:实际使用的索引
  • 含义:MySQL优化器最终选择的、实际执行查询时使用的索引(NULL 表示未使用任何索引)。
  • 关键说明:
    • keypossible_keys 的子集,可能存在 possible_keys 有值但 keyNULL 的情况(如索引选择性过低,MySQL认为使用索引不如全表扫描高效)。
    • 查看该字段可验证索引是否生效,是索引优化的核心判断依据。
8. key_len:索引使用的长度
  • 含义:表示MySQL实际使用的索引长度(单位:字节),反映了索引的使用程度(是否用到了复合索引的所有列)。
  • 计算规则(以UTF-8编码为例):
    • 主键/int类型:4字节(如果是 int unsigned,则5字节)。
    • 字符类型:varchar(n)n*3 + 2(2字节用于存储字符串长度),char(n)n*3
    • 允许为NULL的字段:会额外占用1字节。
  • 作用:
    • 判断复合索引是否被充分利用(如复合索引包含 a,b,c 三列,若 key_len 仅对应 a 列的长度,说明只用到了 a 列,未用到 b,c 列)。
    • 辅助排查索引失效问题(如索引长度异常偏小,可能是索引列被函数/隐式转换修饰,导致索引部分失效)。
9. ref:与索引匹配的列/常量
  • 含义:表示与 key 所指向的索引进行匹配的内容,可以是:
    • 常量(如 const'test' 等固定值)。
    • 表的列(如 a.b_id,表示用表a的b_id列与索引匹配)。
  • 作用:反映索引的匹配方式,ref 为列名时,说明是关联查询或多条件等值查询,性能更优;refconst 时,说明是常量等值查询,性能最佳。
10. rows:预估扫描行数
  • 含义:MySQL优化器预估的、为了得到最终结果需要扫描的行数(非精确值,是基于索引统计信息的估算)。
  • 作用:核心性能参考指标,行数越多,查询耗时越长。百万级表中,若 rows 达到10万+甚至百万级,大概率是慢SQL,需要优化。
11. filtered:过滤后行占比
  • 含义:表示经过 WHERE 条件过滤后,剩余行数占 rows(预估扫描行数)的百分比(取值范围0-100)。
  • 计算逻辑:最终返回行数 ≈ rows * (filtered / 100)
  • 作用:filtered 数值越低,说明 WHERE 条件的过滤效果越差(大量无效数据被扫描后过滤),需要优化 WHERE 条件或给过滤字段创建索引。
12. Extra:额外信息(关键优化提示)
  • 含义:包含MySQL执行查询的额外细节,是判断查询性能瓶颈的重要补充,核心取值如下:
    取值 含义 优化优先级
    Using index 覆盖索引:查询所需字段均在索引中,无需回表查询数据行,性能最优 理想状态,无需优化
    Using where 仅通过索引无法得到结果,需要通过 WHERE 条件过滤数据行 正常场景,无需优化
    Using temporary MySQL需要创建临时表存储中间结果(如 GROUP BY/ORDER BY 无合适索引) 严重性能问题,必须优化
    Using filesort MySQL需要进行文件排序(ORDER BY 字段无索引或索引失效) 严重性能问题,必须优化
    Using join buffer 联表查询时,MySQL使用连接缓冲区存储中间结果(未使用索引联表) 性能问题,需要优化联表索引
    Distinct MySQL去重优化,提前终止索引扫描 正常场景,无需优化
    Range checked for each record 逐行检查索引范围(索引失效),性能极差 紧急优化,重建/调整索引
  • 关键提示:Using temporaryUsing filesort 是慢SQL的“两大杀手”,一旦出现,必须优先优化。

2.3 EXPLAIN 分析流程总结

  1. 先看 type 字段:是否为 ALL(全表扫描),若是则优先优化索引,提升访问类型。
  2. 再看 key 字段:是否为 NULL(未使用索引),若是则检查 possible_keys,针对性创建索引。
  3. 接着看 rows 字段:预估扫描行数是否过大(百万级表中超过1万需警惕)。
  4. 最后看 Extra 字段:是否存在 Using temporary/Using filesort,若是则优先解决这两个问题。
  5. 辅助检查:key_len(索引利用程度)、filtered(过滤效果)、ref(索引匹配方式)。

三、百万级数据环境搭建(表A+表B)

3.1 表结构设计

先创建两张测试表,模拟实际业务场景(表A为“订单表”,表B为“用户表”):

-- 表A:订单表(100万行),order_id为主键,user_id关联表B的user_id,status为订单状态
CREATE TABLE `table_a` (
  `order_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '订单ID(主键)',
  `user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID(关联表B)',
  `order_amount` DECIMAL(10,2) NOT NULL COMMENT '订单金额',
  `status` TINYINT NOT NULL COMMENT '订单状态:1-已支付,2-未支付,3-已取消',
  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`order_id`) -- 仅主键索引,无其他索引(故意留坑,制造慢SQL)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表(100万行)';

-- 表B:用户表(30万行),user_id为主键,user_name为用户名,age为用户年龄
CREATE TABLE `table_b` (
  `user_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID(主键)',
  `user_name` VARCHAR(50) NOT NULL COMMENT '用户名',
  `age` TINYINT UNSIGNED NOT NULL COMMENT '用户年龄',
  `register_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
  PRIMARY KEY (`user_id`) -- 仅主键索引,无其他索引(故意留坑,制造慢SQL)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表(30万行)';

3.2 批量插入百万级数据

通过存储过程批量插入数据(MySQL 8.0版本兼容,5.7版本需微调语法):

1. 插入表B(30万行用户数据)
-- 创建存储过程:批量插入表B数据
DELIMITER //
CREATE PROCEDURE insert_table_b()
BEGIN
  DECLARE i INT DEFAULT 1;
  -- 循环插入30万行
  WHILE i <= 300000 DO
    INSERT INTO `table_b` (`user_name`, `age`, `register_time`)
    VALUES (
      CONCAT('user_', i), -- 用户名:user_1、user_2...
      FLOOR(18 + RAND() * 50), -- 年龄:18-68岁随机
      DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 3650) DAY) -- 注册时间:10年内随机
    );
    -- 每1000行提交一次,避免事务过大
    IF i % 1000 = 0 THEN
      COMMIT;
    END IF;
    SET i = i + 1;
  END WHILE;
  COMMIT;
END //
DELIMITER ;

-- 执行存储过程(耗时约1-3分钟,视服务器性能而定)
CALL insert_table_b();

-- 删除存储过程(可选)
DROP PROCEDURE IF EXISTS insert_table_b;
2. 插入表A(100万行订单数据)
-- 创建存储过程:批量插入表A数据
DELIMITER //
CREATE PROCEDURE insert_table_a()
BEGIN
  DECLARE i INT DEFAULT 1;
  DECLARE random_user_id BIGINT UNSIGNED;
  DECLARE random_status TINYINT;
  DECLARE random_amount DECIMAL(10,2);
  -- 循环插入100万行
  WHILE i <= 1000000 DO
    -- 随机关联表B的user_id(1-300000)
    SET random_user_id = FLOOR(1 + RAND() * 300000);
    -- 随机订单状态(1-3)
    SET random_status = FLOOR(1 + RAND() * 3);
    -- 随机订单金额(1-1000元)
    SET random_amount = ROUND(RAND() * 1000, 2);
    -- 插入数据
    INSERT INTO `table_a` (`user_id`, `order_amount`, `status`, `create_time`)
    VALUES (
      random_user_id,
      random_amount,
      random_status,
      DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 180) DAY) -- 创建时间:6个月内随机
    );
    -- 每1000行提交一次
    IF i % 1000 = 0 THEN
      COMMIT;
    END IF;
    SET i = i + 1;
  END WHILE;
  COMMIT;
END //
DELIMITER ;

-- 执行存储过程(耗时约3-8分钟,视服务器性能而定)
CALL insert_table_a();

-- 删除存储过程(可选)
DROP PROCEDURE IF EXISTS insert_table_a;
3. 验证数据量
-- 查看表A数据量(应返回1000000)
SELECT COUNT(*) FROM table_a;

-- 查看表B数据量(应返回300000)
SELECT COUNT(*) FROM table_b;

四、百万级数据联表慢SQL场景模拟与分析

4.1 慢SQL场景构造

需求:查询“已支付订单(status=1)的用户信息、订单总金额及订单数量”,使用 LEFT JOIN + WHERE + GROUP BY 实现:

-- 慢SQL示例(未优化前)
SELECT 
  b.user_id,
  b.user_name,
  SUM(a.order_amount) AS total_amount, -- 订单总金额
  COUNT(a.order_id) AS order_count     -- 订单数量
FROM table_a a
LEFT JOIN table_b b ON a.user_id = b.user_id
WHERE a.status = 1
GROUP BY b.user_id, b.user_name;
慢SQL现象

执行上述SQL后,会出现以下情况:

  • 执行耗时极长(服务器配置一般的情况下,耗时约10-30秒,甚至更久)。
  • 数据库连接被占用,可能导致其他业务查询阻塞。
  • 通过 SHOW PROCESSLIST 可看到该SQL处于 Sending data 状态(长时间扫描数据)。

4.2 使用EXPLAIN分析慢SQL

执行 EXPLAIN 分析上述SQL:

EXPLAIN
SELECT 
  b.user_id,
  b.user_name,
  SUM(a.order_amount) AS total_amount,
  COUNT(a.order_id) AS order_count
FROM table_a a
LEFT JOIN table_b b ON a.user_id = b.user_id
WHERE a.status = 1
GROUP BY b.user_id, b.user_name;
EXPLAIN 执行计划结果(示例)
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE a NULL ALL NULL NULL NULL NULL 1000000 33.33 Using where; Using temporary; Using filesort
1 SIMPLE b NULL ALL PRIMARY NULL NULL NULL 300000 100.00 Using where; Using join buffer (Block Nested Loop)
执行计划逐行分析(核心瓶颈定位)
第一行(表a)分析
  1. type = ALL:全表扫描,MySQL需要遍历表a的100万行数据,这是慢SQL的核心原因之一。
  2. possible_keys = NULLkey = NULL:没有可选索引,也未使用任何索引,导致全表扫描。
  3. rows = 1000000:预估扫描100万行,扫描行数极大。
  4. filtered = 33.33WHERE a.status = 1 过滤后仅保留约1/3数据,过滤效果差(大量无效数据被扫描后丢弃)。
  5. Extra = Using where; Using temporary; Using filesort
    • Using where:正常过滤,但无索引支撑。
    • Using temporary:需要创建临时表存储 GROUP BY 的中间结果,临时表的创建与销毁会消耗大量资源。
    • Using filesort:需要对 GROUP BY 的字段进行文件排序,排序操作在百万级数据下耗时极长。
第二行(表b)分析
  1. type = ALL:全表扫描,MySQL需要遍历表b的30万行数据,联表效率极低。
  2. possible_keys = PRIMARY:主键(user_id)是候选索引,但未被使用。
  3. key = NULL:未使用主键索引,导致全表扫描。
  4. rows = 300000:预估扫描30万行,联表扫描行数极大。
  5. Extra = Using where; Using join buffer (Block Nested Loop)
    • Using join buffer (Block Nested Loop):使用块嵌套循环连接,需要借助连接缓冲区存储中间数据,联表效率远低于索引嵌套循环(Index Nested Loop),这是联表查询缓慢的核心原因。
整体瓶颈总结
  1. 表a无索引:status 过滤字段、user_id 联表字段均无索引,导致全表扫描+过滤效果差。
  2. 联表无索引支撑:表a的 user_id 与表b的 user_id 联表时,表a的 user_id 无索引,导致MySQL无法使用 eq_ref 高效联表,只能用全表扫描+连接缓冲区。
  3. GROUP BY 无索引支撑:分组字段(b.user_idb.user_name)无索引,导致 Using temporary(临时表)和 Using filesort(文件排序),这两个操作是性能杀手。
  4. 扫描行数过大:表a扫描100万行,表b扫描30万行,整体数据扫描量极大,IO开销过高。

4.3 慢SQL优化方案(分步落地)

优化思路:创建合适的索引 + 优化SQL语句结构 + 消除临时表与文件排序,分3步进行优化。

第一步:创建针对性索引(核心优化)

根据SQL的执行逻辑,创建以下索引:

  1. 表a:创建复合索引 idx_status_user_idstatus + user_id + order_amount
    • 理由:status 是过滤字段(WHERE),user_id 是联表字段(JOIN),order_amount 是聚合字段(SUM),复合索引包含这三个字段,可实现“覆盖索引”,避免回表。
    CREATE INDEX idx_status_user_id ON table_a (`status`, `user_id`, `order_amount`);
    
  2. 表b:无需额外创建索引(主键 user_id 已存在,只需确保联表时使用主键索引即可)。
第二步:优化SQL语句结构
  • 原SQL的 LEFT JOIN 中,WHERE a.status = 1 会将 LEFT JOIN 转为 INNER JOIN(因为过滤了左表数据),可明确改为 INNER JOIN(语义更清晰,优化器可更好地选择执行计划)。
  • 确保 GROUP BY 字段与联表字段一致,减少排序开销。

优化后的SQL:

SELECT 
  b.user_id,
  b.user_name,
  SUM(a.order_amount) AS total_amount,
  COUNT(a.order_id) AS order_count
FROM table_a a
INNER JOIN table_b b ON a.user_id = b.user_id -- 明确转为INNER JOIN
WHERE a.status = 1
GROUP BY b.user_id, b.user_name; -- 分组字段与主键一致,无需额外排序
第三步:验证索引生效(再次执行EXPLAIN)

执行优化后的SQL的 EXPLAIN

EXPLAIN
SELECT 
  b.user_id,
  b.user_name,
  SUM(a.order_amount) AS total_amount,
  COUNT(a.order_id) AS order_count
FROM table_a a
INNER JOIN table_b b ON a.user_id = b.user_id
WHERE a.status = 1
GROUP BY b.user_id, b.user_name;
优化后的执行计划(示例)
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE a NULL ref idx_status_user_id idx_status_user_id 1 const 333333 100.00 Using index
1 SIMPLE b NULL eq_ref PRIMARY PRIMARY 8 test.a.user_id 1 100.00 Using index
优化后执行计划分析(性能提升关键点)
第一行(表a)分析
  1. type = ref:使用普通索引等值查询,性能优异(避免了全表扫描)。
  2. possible_keys = idx_status_user_idkey = idx_status_user_id:索引生效,使用了我们创建的复合索引。
  3. key_len = 1status 是TINYINT类型(1字节),说明先使用 status 字段过滤数据,索引利用合理。
  4. ref = consta.status = 1 是常量等值查询,匹配效率极高。
  5. rows = 333333:预估扫描33.33万行(仅过滤后的有效数据),扫描行数大幅减少。
  6. filtered = 100.00:过滤效果极佳(索引直接过滤,无需扫描无效数据)。
  7. Extra = Using index:覆盖索引生效,查询所需字段(status、user_id、order_amount)均在索引中,无需回表查询数据行,性能大幅提升。
第二行(表b)分析
  1. type = eq_ref:联表时使用主键索引等值匹配,每行表a数据仅匹配表b的1行数据,这是联表的最优方式。
  2. key = PRIMARY:使用了表b的主键索引,索引生效。
  3. key_len = 8user_id 是BIGINT UNSIGNED类型(8字节),主键索引被充分利用。
  4. ref = test.a.user_id:用表a的user_id字段与表b的主键索引匹配,联表效率极高。
  5. rows = 1:预估每行表a数据仅扫描表b的1行数据,联表扫描行数大幅减少。
  6. Extra = Using index:表b的查询字段(user_id、user_name)可通过主键索引覆盖(InnoDB主键索引包含所有字段),无需回表。
优化后性能对比
指标 优化前 优化后 提升效果
执行耗时 10-30秒 0.1-0.5秒 提升20-300倍
表a扫描行数 100万行 33.33万行 减少66.67%
表b扫描行数 30万行 33.33万行(每行仅1行) 联表效率提升30万倍
Extra字段 包含临时表+文件排序 仅覆盖索引 消除性能杀手操作
第四步:进阶优化(可选,针对超大数据量)

若数据量继续增长(如表a达到1000万行),可进一步优化:

  1. 分区表优化:按 create_time 对表a进行分区(如按月份分区),减少扫描的分区数量。
  2. 分库分表:使用Sharding-JDBC等中间件,按 user_id 分表,分散单表数据量。
  3. 预聚合:通过定时任务(如Quartz)预计算聚合结果,存入中间表,查询时直接读取中间表。
  4. 调整MySQL参数:增大 join_buffer_size(连接缓冲区)、sort_buffer_size(排序缓冲区),提升联表与排序效率(需合理配置,避免内存溢出)。

五、EXPLAIN 高级使用技巧

5.1 分析子查询与派生表

对于包含子查询的SQL,EXPLAIN 会显示不同的 idselect_type,例如:

EXPLAIN
SELECT a.order_id, b.user_name
FROM table_a a
WHERE a.user_id IN (
  SELECT b.user_id FROM table_b b WHERE b.age > 30
);
  • 子查询的 select_type = SUBQUERYid = 2,先执行子查询。
  • 若子查询返回数据量大,可改为 JOIN 方式优化,避免 DEPENDENT SUBQUERY(依赖子查询)。

5.2 分析覆盖索引

覆盖索引是性能优化的“黄金法则”,判断标准:

  • Extra 字段包含 Using index
  • 查询的所有字段(SELECT、WHERE、JOIN、GROUP BY、ORDER BY)均在索引中。
  • 避免 SELECT *(容易导致回表,无法使用覆盖索引)。

5.3 分析索引失效场景

通过 EXPLAINkey = NULL 可判断索引失效,常见失效场景:

  1. 索引列被函数修饰(如 WHERE DATE(create_time) = '2024-01-01')。
  2. 索引列发生隐式转换(如 VARCHAR 字段与 INT 常量比较)。
  3. WHERE 条件使用 !=<>NOT IN(可能导致索引失效)。
  4. LIKE 以通配符开头(如 LIKE '%test')。
  5. 复合索引不满足“最左前缀原则”(如复合索引 (a,b,c),仅使用 bc 作为查询条件)。

六、总结

6.1 EXPLAIN 核心要点回顾

  1. EXPLAIN 是慢SQL分析的核心工具,通过12个字段展示执行计划,核心关注 typekeyrowsExtra 四个字段。
  2. type 字段优先级:system > const > eq_ref > ref > range > index > ALL,杜绝 ALL(全表扫描)。
  3. Extra 字段中,Using temporaryUsing filesort 是性能杀手,必须通过索引优化消除。
  4. 索引优化的核心:给 WHERE 过滤字段、JOIN 联表字段、GROUP BY/ORDER BY 分组排序字段创建索引,优先使用复合索引并满足“最左前缀原则”。

6.2 百万级联表慢SQL优化核心流程

  1. 构造场景:搭建百万级数据环境,模拟 LEFT JOIN + WHERE + GROUP BY 慢SQL。
  2. 分析瓶颈:通过 EXPLAIN 定位全表扫描、索引失效、临时表/文件排序等问题。
  3. 索引优化:创建复合索引+覆盖索引,提升扫描与联表效率。
  4. SQL优化:调整SQL结构(如 LEFT JOININNER JOIN),消除无效逻辑。
  5. 进阶优化:分区表、分库分表、预聚合,应对超大数据量场景。

七、附录

7.1 MySQL 常用优化命令

-- 查看慢SQL日志(需开启慢查询日志)
SHOW VARIABLES LIKE 'slow_query_log';
-- 开启慢查询日志(临时生效,重启MySQL失效)
SET GLOBAL slow_query_log = ON;
-- 设置慢查询阈值(单位:秒,默认10秒)
SET GLOBAL long_query_time = 1;

-- 查看当前正在执行的SQL
SHOW PROCESSLIST;

-- 查看索引使用情况
SHOW INDEX FROM table_a;
SHOW INDEX FROM table_b;

-- 分析表(更新索引统计信息,让EXPLAIN更准确)
ANALYZE TABLE table_a;
ANALYZE TABLE table_b;

7.2 注意事项

  1. 批量插入数据时,建议关闭自动提交(SET AUTOCOMMIT = OFF),每1000-10000行提交一次,提升插入效率。
  2. 索引并非越多越好,过多索引会影响插入/更新/删除效率,需按需创建。
  3. EXPLAINrows 字段是预估值,若与实际扫描行数差异较大,可执行 ANALYZE TABLE 更新表统计信息。
  4. 优化后的SQL需进行压力测试(如使用JMeter、SysBench),验证在高并发场景下的性能表现。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐