MySQL 慢 SQL 识别与优化

一、识别慢 SQL

1. 启用慢查询日志

java 复制代码
-- 查看当前慢查询配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- 开启慢查询日志(临时生效)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;  -- 设置慢查询阈值(秒)
SET GLOBAL slow_query_log_file = '/var/lib/mysql/slow.log';

-- 记录未使用索引的查询
SET GLOBAL log_queries_not_using_indexes = 'ON';

-- 永久生效需修改 my.cnf
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/slow.log
long_query_time = 2
log_queries_not_using_indexes = 1
查看慢查询日志内容:使用系统命令查看日志文件(Linux)

假设你的日志文件在 /var/lib/mysql/slow.log,可以直接用 tailcatless 查看:

java 复制代码
tail -f /var/lib/mysql/slow.log   # 实时查看新增的日志
cat /var/lib/mysql/slow.log       # 查看全部内容
less /var/lib/mysql/slow.log      # 分页查看

示例日志格式如下:

java 复制代码
# Time: 2025-06-20T17:30:45.123456Z
# User@Host: root[root] @ localhost []
# Query_time: 3.123456  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 1000000
SET timestamp=1750486245;
SELECT * FROM users WHERE username = 'test';
  • Query_time : 查询耗时(秒),超过 long_query_time 才会被记录。
  • Rows_examined: 扫描行数,越大越可能需要优化。
  • SQL语句: 实际执行的 SQL。

2. 使用性能分析工具

java 复制代码
-- 查看当前运行中的慢查询
SHOW FULL PROCESSLIST;

-- 使用 EXPLAIN 分析执行计划
EXPLAIN SELECT * FROM orders WHERE customer_id = 1000;

-- 使用 EXPLAIN ANALYZE(MySQL 8.0+)
EXPLAIN ANALYZE SELECT * FROM products WHERE price > 100;

-- 性能分析(临时启用)
SET profiling = 1;
SELECT * FROM large_table;
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;

3.使用 EXPLAIN 分析 SQL 执行计划的详细指南

基础用法(直接在 SQL 前加 EXPLAIN
java 复制代码
EXPLAIN SELECT * 
FROM orders 
WHERE user_id = 100 
  AND status = 'completed' 
ORDER BY created_at DESC 
LIMIT 10;

输出结果示例及关键列解析:

id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE orders NULL ref idx_user_id idx_user 4 const 500 10.00 Using where; Using filesort
核心列深度解析:
  1. type(访问类型) - 最重要指标

    • 性能排序:system > const > eq_ref > ref > range > index > ALL

    • 优化目标:至少达到 range 级别,避免 ALL(全表扫描)

    • 示例诊断:ref 表示使用了非唯一索引

  2. key(实际使用索引)

    • 检查是否使用预期索引:idx_user(实际使用) vs idx_user_status(可能更好的索引)

    • 若为 NULL 表示未使用索引 → 需优化

  3. rows(扫描行数)

    • 预估扫描 500 行 → 结合 filtered 列,实际返回约 500 × 10% = 50 行

    • 目标:减少该值

  4. Extra(额外信息) - 问题高发区

    • Using filesort:手动文件排序(需优化)

    • Using temporary:创建临时表(需优化)

    • Using index:覆盖索引(好现象)

    • Using where:存储引擎返回后再次过滤

类型 (type) 性能排序 含义描述 典型场景 扫描方式 扫描行数 是否用索引 优化建议
system ★★★★★ 最佳 系统表/仅有一行记录 • MyISAM/Inemory引擎的单行系统表 • 衍生表只有一行数据 直接访问系统记录 1 无需优化
const ★★★★☆ 极优 主键/唯一索引的单行访问 WHERE id = 1 (主键查询) • WHERE unique_col = 'value' (唯一索引) 常量扫描 直接定位单行 1 确保主键/唯一索引有效
eq_ref ★★★★☆ 极优 主键关联查询 (多表JOIN时) JOIN ... ON t1.primary_key = t2.primary_key • 驱动表每行匹配被驱动表主键 唯一索引扫描 每行只匹配一次 1/N (N=驱动表行数) 确保JOIN字段是主键或唯一索引
ref ★★★☆☆ 良好 非唯一索引的等值查询 WHERE index_col = 'value' • 普通索引关联查询 • 最左前缀匹配查询 索引范围扫描 可能返回多行 N (匹配行数) 提升索引选择性 添加组合索引
range ★★☆☆☆ 中等 索引范围扫描 WHERE id > 100BETWEEN 100 AND 200IN (1,2,3)LIKE 'prefix%' 索引部分扫描 指定范围内遍历 M (范围行数) 控制扫描行数<1万 避免大范围扫描
index ★☆☆☆☆ 较差 全索引扫描 SELECT indexed_col FROM table (覆盖索引) • ORDER BY indexed_col (无WHERE条件) • 索引全扫描 遍历整个索引树 不读数据文件 全索引 (索引条目数) 确认是否需回表 检查排序必要性
ALL ⚠️ 最差 全表扫描 • 无索引字段查询:WHERE non_index_col=... • 前导通配符:LIKE '%value%' • 未优化的JOIN条件 逐行扫描数据文件 性能灾难 全表 (数据行数) 紧急优化! • 添加索引 • 重写SQL • 限制结果集

通过 EXPLAIN 分析后,若出现 ALLindex 且扫描行数>1000,需优化

  1. 优先关注 type 避免全表扫描

  2. 重点检查 Extra 消除警告项

  3. 通过 rows 评估执行成本

  4. 结合 key_len 判断索引利用率

  5. 每次优化后必须重新执行 EXPLAIN 验证效果

二、慢 SQL 的常见原因

1. 索引问题

java 复制代码
-- 缺失索引
SELECT * FROM users WHERE last_name = 'Smith'; -- 无索引

-- 索引失效
SELECT * FROM orders WHERE YEAR(order_date) = 2023; -- 函数导致索引失效

2. 全表扫描

java 复制代码
-- 未使用索引导致全表扫描
SELECT * FROM products WHERE category LIKE '%electronics%';

3. 复杂 JOIN

java 复制代码
-- 多表 JOIN 未优化
SELECT * 
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN products p ON o.product_id = p.id
WHERE c.country = 'USA' AND p.price > 100;

4. 大表分页

java 复制代码
-- 低效的分页查询
SELECT * FROM logs ORDER BY id LIMIT 1000000, 20;

5.低效子查询

java 复制代码
-- 低效的子查询
SELECT * FROM orders 
WHERE customer_id IN (
    SELECT id FROM customers WHERE country = 'China'
);

三、慢 SQL 优化方案

1. 索引优化策略

java 复制代码
-- 添加必要索引
CREATE INDEX idx_last_name ON users(last_name);
CREATE INDEX idx_order_date ON orders(order_date);

-- 使用覆盖索引
SELECT customer_id, order_date FROM orders; -- 建立 (customer_id, order_date) 索引

-- 索引合并优化
SELECT * FROM products 
WHERE category_id = 5 OR price > 100; -- 分别建立两个索引

-- 强制索引使用
SELECT * FROM orders FORCE INDEX (idx_order_date) 
WHERE order_date BETWEEN '2023-01-01' AND '2023-01-31';

2. SQL 重写

java 复制代码
-- 避免 SELECT * 
SELECT id, name, email FROM users;

-- 分页优化
SELECT * FROM logs WHERE id > 1000000 ORDER BY id LIMIT 20;

-- JOIN 优化
SELECT o.id, c.name, p.product_name 
FROM orders o
FORCE INDEX (idx_customer) 
JOIN customers c ON o.customer_id = c.id
JOIN products p ON o.product_id = p.id
WHERE c.country = 'USA';

-- 子查询转 JOIN
SELECT o.* 
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE c.country = 'China';

-- 避免函数操作
SELECT * FROM orders 
WHERE order_date >= '2023-01-01' 
AND order_date < '2023-02-01';

3. 表结构优化

数据库分库分表:垂直拆分/水平拆分

4. 架构级优化

  • 读写分离:写操作到主库,读操作到从库

  • 缓存层:使用 Redis 缓存热点数据

  • 搜索引擎:复杂查询使用 Elasticsearch

相关推荐
喵手2 分钟前
使用 Java 集合进行缓存系统设计的实践分享!
java·开发语言·缓存
麻衣带我去上学3 分钟前
Spring依赖注入源码学习:基于注解的DI源码解析
java·后端·spring
mldong8 分钟前
mldong 快速开发框架字典模块设计与实现
java·后端·架构
小猫咪怎么会有坏心思呢11 分钟前
华为OD机考-最小循环子数组-字符串(JAVA 2025B卷)
java·开发语言·华为od
岁忧17 分钟前
(LeetCode 面试经典 150 题) 169. 多数元素(哈希表 || 二分查找)
java·c++·算法·leetcode·go·散列表
YuTaoShao18 分钟前
【LeetCode 热题 100】15. 三数之和——排序 + 双指针解法
java·算法·leetcode·职场和发展
巴巴_羊24 分钟前
React JSX原理
java
迢迢星万里灬29 分钟前
Java求职者面试指南:微服务技术与源码原理深度解析
java·spring cloud·微服务·dubbo·netty·分布式系统·面试指南
我崽不熬夜41 分钟前
如何让你的Java代码更加优雅且高效?你也许忽视了这些细节!
java·后端·java ee
Pitayafruit1 小时前
后端开发必看:零代码实现存量服务改造成MCP服务
java·后端·mcp