1. 慢查询问题诊断的核心思路
数据库查询性能问题就像医院里的疑难杂症,需要系统性的诊断方法。我处理过数百例SQL性能案例,发现80%的慢查询问题都集中在索引缺失、统计信息不准、执行计划偏差这三个方面。诊断过程需要像老中医"望闻问切"一样,先观察症状表现,再通过工具检查,最后定位病因。
典型的诊断流程应该包括:症状采集(发现慢查询)→ 初步检查(执行计划分析)→ 深度诊断(资源消耗剖析)→ 治疗方案(优化措施)。这个过程中,最重要的不是工具使用,而是建立正确的分析思维框架。
2. 慢查询发现与捕获方法
2.1 数据库慢查询日志配置
MySQL的慢查询日志是最直接的诊断工具。建议在生产环境这样配置:
sql复制-- 永久生效配置(需要重启)
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1 -- 超过1秒的查询
log_queries_not_using_indexes = 1 -- 记录未使用索引的查询
log_throttle_queries_not_using_indexes = 10 -- 限制每分钟记录数量
-- 临时设置(立即生效)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
注意:开启未使用索引查询日志时务必设置节流阈值,否则高并发时可能影响性能
2.2 实时监控与性能模式
对于不能立即重启的生产系统,performance_schema是更好的选择:
sql复制-- 开启性能监控
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES'
WHERE NAME LIKE 'events_statements%';
-- 查询当前运行的长事务
SELECT * FROM performance_schema.events_statements_current
WHERE TIMER_WAIT > 1000000000; -- 纳秒单位,此处为1秒
-- 历史慢查询分析
SELECT * FROM performance_schema.events_statements_history_long
ORDER BY TIMER_WAIT DESC LIMIT 10;
3. 执行计划深度解析
3.1 EXPLAIN输出关键解读
拿到慢查询后,第一件事就是分析执行计划。这是最容易被误读的部分:
sql复制EXPLAIN FORMAT=JSON
SELECT * FROM orders WHERE user_id = 100 AND status = 'pending';
重点关注这些指标:
- type列:从优到差 system > const > eq_ref > ref > range > index > ALL
- possible_keys vs key:理论上可用索引 vs 实际使用索引
- rows:预估检查行数(与实际行数偏差超过10倍说明统计信息不准)
- Extra列:
- "Using filesort":需要内存排序
- "Using temporary":创建了临时表
- "Using where":存储引擎返回数据后还需过滤
3.2 执行计划可视化工具
对于复杂查询,推荐使用:
- MySQL Workbench的可视化解释功能
- Percona的pt-visual-explain工具
- 阿里云的DAS执行计划分析
这些工具能直观展示执行树和数据流向,特别适合多表关联查询的分析。
4. 性能瓶颈定位技巧
4.1 资源消耗分析
慢查询不一定是SQL本身问题,可能是资源争用导致:
sql复制-- 查看当前锁等待
SELECT * FROM sys.innodb_lock_waits;
-- 查看线程状态
SHOW PROCESSLIST;
-- 查看表打开情况
SHOW OPEN TABLES WHERE In_use > 0;
-- InnoDB状态快照
SHOW ENGINE INNODB STATUS\G
4.2 性能剖析工具
使用profiling工具定位耗时操作:
sql复制-- 开启会话级profiling
SET profiling = 1;
执行你的SQL;
SHOW PROFILE;
SHOW PROFILE CPU FOR QUERY 1;
-- 更细粒度的performance_schema分析
SELECT * FROM performance_schema.events_stages_history_long
WHERE NESTING_EVENT_ID = [你的查询事件ID];
5. 典型优化场景实战
5.1 索引失效的常见陷阱
我遇到过最隐蔽的索引失效案例:
sql复制-- 看似合理的查询,但索引失效
SELECT * FROM users WHERE DATE(create_time) = '2023-01-01';
-- 优化方案
SELECT * FROM users
WHERE create_time >= '2023-01-01 00:00:00'
AND create_time < '2023-01-02 00:00:00';
其他常见陷阱:
- 隐式类型转换(varchar列用数字查询)
- 函数操作列(WHERE UPPER(name) = 'ABC')
- 前导模糊查询(LIKE '%keyword')
5.2 分页查询优化
深度分页是性能杀手:
sql复制-- 低效写法
SELECT * FROM large_table ORDER BY id LIMIT 1000000, 10;
-- 优化方案1:延迟关联
SELECT t.* FROM large_table t
JOIN (SELECT id FROM large_table ORDER BY id LIMIT 1000000, 10) tmp
ON t.id = tmp.id;
-- 优化方案2:书签记录
SELECT * FROM large_table
WHERE id > 上次最后一条ID
ORDER BY id LIMIT 10;
6. 高级诊断技术
6.1 Optimizer Trace分析
当执行计划不符合预期时,可以查看优化器决策过程:
sql复制SET optimizer_trace="enabled=on";
执行你的SQL;
SELECT * FROM information_schema.optimizer_trace;
SET optimizer_trace="enabled=off";
输出是JSON格式,重点关注:
- "considered_execution_plans":评估过的执行计划
- "attaching_conditions_to_tables":条件下推情况
- "final_planning_decision":最终选择原因
6.2 索引效率评估
通过sys库评估索引使用情况:
sql复制-- 查看冗余索引
SELECT * FROM sys.schema_redundant_indexes;
-- 查看未使用索引
SELECT * FROM sys.schema_unused_indexes;
-- 索引统计信息
SELECT * FROM mysql.innodb_index_stats
WHERE table_name = '你的表名';
7. 实战问题排查案例
最近处理的一个生产案例:某查询平时执行很快,但偶尔突然变慢。
排查过程:
- 通过performance_schema发现变慢时ROWS_EXAMINED激增
- 检查执行计划发现有时使用了错误的索引
- 分析发现是统计信息不准确导致
- 解决方案:
sql复制-- 增加统计信息采样率
SET GLOBAL innodb_stats_persistent_sample_pages = 200;
ANALYZE TABLE 问题表;
8. 性能优化检查清单
这是我总结的慢查询优化自检表:
- [ ] 是否收集了完整的查询文本(包括事务内多条SQL)
- [ ] 是否在不同时段多次检查执行计划
- [ ] 是否验证了统计信息的准确性
- [ ] 是否检查了锁等待情况
- [ ] 是否考虑了并发执行的影响
- [ ] 是否测试了优化方案的真实效果
9. 工具链推荐
完整的性能分析工具栈:
- 采集:pt-query-digest、MySQL Enterprise Monitor
- 分析:Percona PMM、VividCortex
- 测试:sysbench、mysqlslap
- 可视化:Grafana + Prometheus
- 压测:JMeter、Locust
10. 保持性能的最佳实践
最后分享几个保持数据库性能的习惯:
- 定期检查慢查询日志(建议每周)
- 对核心表执行ANALYZE TABLE(数据变化超过10%时)
- 监控关键指标:索引命中率、缓冲池命中率
- 新上线SQL必须经过执行计划审查
- 建立SQL性能基准,定期回归测试