Mysql explain,尝试给表A写100万行数据,表B写30万数据,使用LEFT JOIN, WHERE, GROUP BY 时,分析慢SQL案例总结。
在后端开发与数据库优化工作中,EXPLAIN是排查慢SQL的“瑞士军刀”——它能直观展示SQL的执行计划,让我们清晰看到MySQL如何解析SQL、如何选择索引、如何执行联表/分组/过滤操作,从而精准定位性能瓶颈。先看type字段:是否为ALL(全表扫描),若是则优先优化索引,提升访问类型。再看key字段:是否为NULL(未使用索引),若是则检查,针对性创建索引。接着看rows字段:预估扫描行数是否
一、前言
在后端开发与数据库优化工作中,EXPLAIN 是排查慢SQL的“瑞士军刀”——它能直观展示SQL的执行计划,让我们清晰看到MySQL如何解析SQL、如何选择索引、如何执行联表/分组/过滤操作,从而精准定位性能瓶颈。
二、MySQL EXPLAIN 详解
2.1 EXPLAIN 基础用法
EXPLAIN 用于分析SQL语句的执行计划,支持 SELECT、DELETE、UPDATE、INSERT(部分MySQL版本)语句,核心用法有两种:
- 基础用法:
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; - 扩展用法:
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_a、table_b)。 - 别名(如SQL中定义的
a、b)。 - 派生表标识(如
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 - 关键取值详解:
system:最优,表中只有1行数据(系统表),几乎不会出现(仅系统表或临时表中1行数据时触发)。const:表中数据通过主键/唯一索引等值查询,最多返回1行数据,MySQL会将其作为常量处理(如SELECT * FROM table_a WHERE id = 1,id是主键)。eq_ref:多表联表时,被联表通过主键/唯一索引等值匹配,每行主表数据仅匹配被联表1行数据(LEFT JOIN/INNER JOIN的最优联表方式,如a.b_id = b.id,b.id是主键)。ref:通过普通索引等值查询,可能返回多行数据(如SELECT * FROM table_a WHERE name = 'test',name是普通索引)。range:索引范围查询,如>、<、BETWEEN、IN(非全索引)、LIKE 'test%'(前缀匹配),仅扫描索引的某一范围,比全索引扫描更优。index:全索引扫描,MySQL遍历整个索引但不扫描数据行(索引包含查询所需所有字段,即“覆盖索引”场景),比全表扫描快。ALL:最差,全表扫描(Full Table Scan),MySQL会遍历整个表的数据行,百万级数据下会导致极慢的查询。
- 优化目标:至少保证
type达到range级别,最好是ref或eq_ref,杜绝ALL(全表扫描)。
6. possible_keys:可能使用的索引
- 含义:MySQL优化器认为当前查询可能用到的索引列表(仅为“候选”,不一定实际使用)。
- 作用:如果该字段为
NULL,说明当前查询没有可选索引,大概率会触发全表扫描,需要针对性创建索引。
7. key:实际使用的索引
- 含义:MySQL优化器最终选择的、实际执行查询时使用的索引(
NULL表示未使用任何索引)。 - 关键说明:
key是possible_keys的子集,可能存在possible_keys有值但key为NULL的情况(如索引选择性过低,MySQL认为使用索引不如全表扫描高效)。- 查看该字段可验证索引是否生效,是索引优化的核心判断依据。
8. key_len:索引使用的长度
- 含义:表示MySQL实际使用的索引长度(单位:字节),反映了索引的使用程度(是否用到了复合索引的所有列)。
- 计算规则(以UTF-8编码为例):
- 主键/int类型:4字节(如果是
int unsigned,则5字节)。 - 字符类型:
varchar(n)为n*3 + 2(2字节用于存储字符串长度),char(n)为n*3。 - 允许为NULL的字段:会额外占用1字节。
- 主键/int类型:4字节(如果是
- 作用:
- 判断复合索引是否被充分利用(如复合索引包含
a,b,c三列,若key_len仅对应a列的长度,说明只用到了a列,未用到b,c列)。 - 辅助排查索引失效问题(如索引长度异常偏小,可能是索引列被函数/隐式转换修饰,导致索引部分失效)。
- 判断复合索引是否被充分利用(如复合索引包含
9. ref:与索引匹配的列/常量
- 含义:表示与
key所指向的索引进行匹配的内容,可以是:- 常量(如
const、'test'等固定值)。 - 表的列(如
a.b_id,表示用表a的b_id列与索引匹配)。
- 常量(如
- 作用:反映索引的匹配方式,
ref为列名时,说明是关联查询或多条件等值查询,性能更优;ref为const时,说明是常量等值查询,性能最佳。
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 temporary和Using filesort是慢SQL的“两大杀手”,一旦出现,必须优先优化。
2.3 EXPLAIN 分析流程总结
- 先看
type字段:是否为ALL(全表扫描),若是则优先优化索引,提升访问类型。 - 再看
key字段:是否为NULL(未使用索引),若是则检查possible_keys,针对性创建索引。 - 接着看
rows字段:预估扫描行数是否过大(百万级表中超过1万需警惕)。 - 最后看
Extra字段:是否存在Using temporary/Using filesort,若是则优先解决这两个问题。 - 辅助检查:
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)分析
type = ALL:全表扫描,MySQL需要遍历表a的100万行数据,这是慢SQL的核心原因之一。possible_keys = NULL、key = NULL:没有可选索引,也未使用任何索引,导致全表扫描。rows = 1000000:预估扫描100万行,扫描行数极大。filtered = 33.33:WHERE a.status = 1过滤后仅保留约1/3数据,过滤效果差(大量无效数据被扫描后丢弃)。Extra = Using where; Using temporary; Using filesort:Using where:正常过滤,但无索引支撑。Using temporary:需要创建临时表存储GROUP BY的中间结果,临时表的创建与销毁会消耗大量资源。Using filesort:需要对GROUP BY的字段进行文件排序,排序操作在百万级数据下耗时极长。
第二行(表b)分析
type = ALL:全表扫描,MySQL需要遍历表b的30万行数据,联表效率极低。possible_keys = PRIMARY:主键(user_id)是候选索引,但未被使用。key = NULL:未使用主键索引,导致全表扫描。rows = 300000:预估扫描30万行,联表扫描行数极大。Extra = Using where; Using join buffer (Block Nested Loop):Using join buffer (Block Nested Loop):使用块嵌套循环连接,需要借助连接缓冲区存储中间数据,联表效率远低于索引嵌套循环(Index Nested Loop),这是联表查询缓慢的核心原因。
整体瓶颈总结
- 表a无索引:
status过滤字段、user_id联表字段均无索引,导致全表扫描+过滤效果差。 - 联表无索引支撑:表a的
user_id与表b的user_id联表时,表a的user_id无索引,导致MySQL无法使用eq_ref高效联表,只能用全表扫描+连接缓冲区。 GROUP BY无索引支撑:分组字段(b.user_id、b.user_name)无索引,导致Using temporary(临时表)和Using filesort(文件排序),这两个操作是性能杀手。- 扫描行数过大:表a扫描100万行,表b扫描30万行,整体数据扫描量极大,IO开销过高。
4.3 慢SQL优化方案(分步落地)
优化思路:创建合适的索引 + 优化SQL语句结构 + 消除临时表与文件排序,分3步进行优化。
第一步:创建针对性索引(核心优化)
根据SQL的执行逻辑,创建以下索引:
- 表a:创建复合索引
idx_status_user_id(status+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`); - 理由:
- 表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)分析
type = ref:使用普通索引等值查询,性能优异(避免了全表扫描)。possible_keys = idx_status_user_id、key = idx_status_user_id:索引生效,使用了我们创建的复合索引。key_len = 1:status是TINYINT类型(1字节),说明先使用status字段过滤数据,索引利用合理。ref = const:a.status = 1是常量等值查询,匹配效率极高。rows = 333333:预估扫描33.33万行(仅过滤后的有效数据),扫描行数大幅减少。filtered = 100.00:过滤效果极佳(索引直接过滤,无需扫描无效数据)。Extra = Using index:覆盖索引生效,查询所需字段(status、user_id、order_amount)均在索引中,无需回表查询数据行,性能大幅提升。
第二行(表b)分析
type = eq_ref:联表时使用主键索引等值匹配,每行表a数据仅匹配表b的1行数据,这是联表的最优方式。key = PRIMARY:使用了表b的主键索引,索引生效。key_len = 8:user_id是BIGINT UNSIGNED类型(8字节),主键索引被充分利用。ref = test.a.user_id:用表a的user_id字段与表b的主键索引匹配,联表效率极高。rows = 1:预估每行表a数据仅扫描表b的1行数据,联表扫描行数大幅减少。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万行),可进一步优化:
- 分区表优化:按
create_time对表a进行分区(如按月份分区),减少扫描的分区数量。 - 分库分表:使用Sharding-JDBC等中间件,按
user_id分表,分散单表数据量。 - 预聚合:通过定时任务(如Quartz)预计算聚合结果,存入中间表,查询时直接读取中间表。
- 调整MySQL参数:增大
join_buffer_size(连接缓冲区)、sort_buffer_size(排序缓冲区),提升联表与排序效率(需合理配置,避免内存溢出)。
五、EXPLAIN 高级使用技巧
5.1 分析子查询与派生表
对于包含子查询的SQL,EXPLAIN 会显示不同的 id 和 select_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 = SUBQUERY,id = 2,先执行子查询。 - 若子查询返回数据量大,可改为
JOIN方式优化,避免DEPENDENT SUBQUERY(依赖子查询)。
5.2 分析覆盖索引
覆盖索引是性能优化的“黄金法则”,判断标准:
Extra字段包含Using index。- 查询的所有字段(SELECT、WHERE、JOIN、GROUP BY、ORDER BY)均在索引中。
- 避免
SELECT *(容易导致回表,无法使用覆盖索引)。
5.3 分析索引失效场景
通过 EXPLAIN 的 key = NULL 可判断索引失效,常见失效场景:
- 索引列被函数修饰(如
WHERE DATE(create_time) = '2024-01-01')。 - 索引列发生隐式转换(如
VARCHAR字段与INT常量比较)。 WHERE条件使用!=、<>、NOT IN(可能导致索引失效)。LIKE以通配符开头(如LIKE '%test')。- 复合索引不满足“最左前缀原则”(如复合索引
(a,b,c),仅使用b或c作为查询条件)。
六、总结
6.1 EXPLAIN 核心要点回顾
EXPLAIN是慢SQL分析的核心工具,通过12个字段展示执行计划,核心关注type、key、rows、Extra四个字段。type字段优先级:system>const>eq_ref>ref>range>index>ALL,杜绝ALL(全表扫描)。Extra字段中,Using temporary和Using filesort是性能杀手,必须通过索引优化消除。- 索引优化的核心:给
WHERE过滤字段、JOIN联表字段、GROUP BY/ORDER BY分组排序字段创建索引,优先使用复合索引并满足“最左前缀原则”。
6.2 百万级联表慢SQL优化核心流程
- 构造场景:搭建百万级数据环境,模拟
LEFT JOIN + WHERE + GROUP BY慢SQL。 - 分析瓶颈:通过
EXPLAIN定位全表扫描、索引失效、临时表/文件排序等问题。 - 索引优化:创建复合索引+覆盖索引,提升扫描与联表效率。
- SQL优化:调整SQL结构(如
LEFT JOIN转INNER JOIN),消除无效逻辑。 - 进阶优化:分区表、分库分表、预聚合,应对超大数据量场景。
七、附录
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 注意事项
- 批量插入数据时,建议关闭自动提交(
SET AUTOCOMMIT = OFF),每1000-10000行提交一次,提升插入效率。 - 索引并非越多越好,过多索引会影响插入/更新/删除效率,需按需创建。
EXPLAIN的rows字段是预估值,若与实际扫描行数差异较大,可执行ANALYZE TABLE更新表统计信息。- 优化后的SQL需进行压力测试(如使用JMeter、SysBench),验证在高并发场景下的性能表现。
更多推荐


所有评论(0)