慢查询问题诊断的起步动作
数据库运维中,慢查询是最常见的性能瓶颈源头。MySQL 8提供了完善的慢查询日志机制,但很多线上环境没有正确开启,或者配置了过大的阈值导致漏掉关键信息。第一步永远是把慢查询日志打开,把阈值设低:
# my.cnf 核心配置
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5 # 超过0.5秒记录
log_queries_not_using_indexes = 1 # 没走索引的也记录
min_examined_row_limit = 100 # 扫描行少于100的不记录,过滤噪声
# 在线设置(无需重启)
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 0.5;
SET GLOBAL log_queries_not_using_indexes = 1;
开启后,用mysqldumpslow或pt-query-digest做聚合分析:
# 按查询时间排序Top10
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# Percona Toolkit更强大的分析
pt-query-digest /var/log/mysql/slow.log > slow_report.txt
EXPLAIN执行计划深度解读
拿到慢SQL后,EXPLAIN是第一诊断工具。MySQL 8的EXPLAIN FORMAT=TREE和FORMAT=JSON提供更丰富的信息:
# 标准EXPLAIN
EXPLAIN SELECT o.*, u.name FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending' AND o.created_at > '2026-07-01';
# JSON格式(含成本估算)
EXPLAIN FORMAT=JSON SELECT ...;
# 树形格式(MySQL 8.0.16+)
EXPLAIN FORMAT=TREE SELECT ...;
# 实际执行统计(能看到真实行数)
EXPLAIN ANALYZE SELECT ...;
重点关注这5列:
- type:访问类型,从好到差:system > const > eq_ref > ref > range > index > ALL。出现ALL必须优化
- key:实际使用的索引,NULL表示没走索引
- rows:预估扫描行数,越大越慢
- filtered:过滤比例,100%表示完全利用了索引,1%表示99%的行被丢弃
- Extra:Using filesort和Using temporary是性能杀手,Using index(覆盖索引)是理想状态
索引优化:从缺失索引到复合索引设计
缺失索引识别
# 查看表的索引使用统计
SELECT * FROM sys.schema_unused_indexes
WHERE object_schema = 'your_db';
# 查看冗余索引
SELECT * FROM sys.schema_redundant_indexes
WHERE table_schema = 'your_db';
# 查看索引建议(基于执行历史)
SELECT * FROM sys.schema_index_statistics
WHERE table_schema = 'your_db'
ORDER BY ROWS_READ DESC LIMIT 10;
复合索引的最左前缀原则
# 典型查询模式
SELECT * FROM orders
WHERE user_id = 100 AND status = 'pending'
ORDER BY created_at DESC
LIMIT 20;
# 错误索引:三个单列索引,优化器可能只选一个
CREATE INDEX idx_user ON orders(user_id);
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_created ON orders(created_at);
# 正确索引:覆盖查询条件的复合索引
CREATE INDEX idx_user_status_created
ON orders(user_id, status, created_at);
# EXPLAIN验证
type: ref
key: idx_user_status_created
Extra: Using index condition; Backward index scan
复合索引的字段顺序遵循等值条件在前、范围条件在后、排序字段最后的原则。MySQL 8支持降序索引,ORDER BY created_at DESC可以直接走索引排序,避免filesort。
覆盖索引:消除回表开销
# 查询只返回user_id和status,不需要回表
SELECT user_id, status FROM orders
WHERE user_id = 100 AND status = 'pending';
# 覆盖索引:索引包含所有查询字段
CREATE INDEX idx_covering
ON orders(user_id, status, id); # id是主键,自动包含
# EXPLAIN验证
Extra: Using index ← 这表示覆盖索引,无需回表
SQL查询优化典型案例
案例一:子查询转JOIN
# 慢写法:相关子查询,每行执行一次子查询
SELECT * FROM orders o
WHERE o.amount > (
SELECT AVG(amount) FROM orders WHERE user_id = o.user_id
);
# 快写法:JOIN + 派生表
SELECT o.* FROM orders o
JOIN (
SELECT user_id, AVG(amount) as avg_amount
FROM orders GROUP BY user_id
) avg ON o.user_id = avg.user_id
WHERE o.amount > avg.avg_amount;
案例二:避免索引失效的常见错误
# 错误:对索引列使用函数
WHERE YEAR(created_at) = 2026 # 索引失效
# 正确:范围查询
WHERE created_at >= '2026-01-01'
AND created_at < '2027-01-01' # 走索引range扫描
# 错误:隐式类型转换
WHERE varchar_col = 123 # 索引失效
# 正确:类型一致
WHERE varchar_col = '123' # 走索引
# 错误:LIKE前缀通配符
WHERE name LIKE '%zhang%' # 索引失效
# 正确:前缀匹配
WHERE name LIKE 'zhang%' # 走索引range扫描
InnoDB Buffer Pool调优
SQL和索引优化做到位后,Buffer Pool配置是下一个性能杠杆:
# my.cnf
innodb_buffer_pool_size = 8G # 专用服务器建议70-80%总内存
innodb_buffer_pool_instances = 8 # 多实例减少锁争用
innodb_read_ahead_threshold = 56 # 预读阈值
# 在线查看Buffer Pool命中率
SELECT
1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) as hit_rate;
# 命中率低于95%说明Buffer Pool不够大
# 查看当前配置
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
MySQL性能调优没有银弹,核心方法就是:慢查询日志抓出问题SQL → EXPLAIN定位执行计划缺陷 → 针对性建索引或改写SQL → Buffer Pool兜底保障I/O性能。每一步都有工具和方法论支撑,不要凭感觉调参。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql8-man-cha-xun-zhen-duan-yu-suo-yin-you-hua-shi-zhan/