一、认识慢查询
1.1 定义
SQL 执行时间超过 long_query_time 阈值的语句,会被记录到慢查询日志,用于事后排查。
- 默认值 10 秒,生产建议 1 秒。
- log_queries_not_using_indexes 可额外抓取未走索引的语句。
- 慢不等于错:排查要找性价比最高的那条,而不是最慢的那条。
1.2 阈值怎么定(行业参考)
| 业务类型 |
建议阈值 |
典型场景 |
| 普通业务系统 |
1 ~ 2 秒 |
OA 办公、数据报表 |
| 电商领域 |
0.5 ~ 1 秒 |
ERP(销售/库存/采购)、商城、医院挂号、制造业 MES |
| 专项高压业务 |
0.1 ~ 0.5 秒 |
证券、银行核心、保险投保理赔 |
1.3 危害
- 长期占用连接,拖垮连接池。
- 持锁时间久,引发连锁阻塞。
- 大事务回放慢,拖出主从延迟。
1.4 三种慢法
| 类型 |
典型成因 |
| 偶发慢 |
锁等待、统计信息抖动 |
| 持续慢 |
缺索引、写法差 |
| 突发慢 |
数据量增长、并发突增、参数变更 |
二、慢查询日志
2.1 常用命令速查
SHOW VARIABLES LIKE 'slow_query_log'; -- 是否开启
SHOW VARIABLES LIKE 'slow_query_log_file'; -- 日志文件位置
SHOW GLOBAL STATUS LIKE 'Slow_queries'; -- 慢查询累计数量
SET GLOBAL slow_query_log = ON; -- 即时开启
SET GLOBAL long_query_time = 1; -- 设置阈值(秒)
2.2 开启三步
- slow_query_log 设为 ON。
- long_query_time 设为 1(按业务场景调整)。
- log_output 选 FILE 或 TABLE;用 min_examined_row_limit 过滤小表扫描噪音。
2.3 日志记录的关键字段
| 字段 |
含义 |
| Query_time |
实际执行耗时 |
| Lock_time |
锁等待耗时 |
| Rows_sent / Rows_examined |
返回行数 / 扫描行数,比值越低越好 |
另含时间戳、用户、主机等上下文信息。
2.4 扩展参数
- log_slow_extra:8.0 起记录更多指标(如 Rows_affected、Bytes_sent)。
- log_slow_admin_statements:记录 DDL 等管理语句。
- log_slow_replica_statements:从库也记录。
2.5 注意事项
- SET GLOBAL 即时生效,无需重启,但需写入配置文件才能持久化。
- log_queries_not_using_indexes 不要盲开,日志量会被放大。
- slow.log 持续增长,需要配置轮转与定期清理。
三、分析工具
| 工具 |
定位 |
要点 |
| mysqldumpslow |
自带、轻量 |
-s t 按总耗时排序,-t 10 取前十;聚合粗糙,精度有限 |
| pt-query-digest |
生产首选 |
按语句指纹聚合,输出 95 分位与样本,适合周报复盘 |
| performance_schema |
8.0 优先 |
查 events_statements_summary_by_digest,按 sum_timer_wait 排序取 TopN,比日志实时 |
| sys schema |
场景化视图 |
statements_with_full_table_scans、statements_sorting_using_temp_tables |
补充:分析主要靠三件套------慢查询日志(找慢 SQL)、mysqldumpslow(聚合统计)、EXPLAIN(看是否合理使用索引)。生产环境下建议把 pt-query-digest 作为标准入口。
四、EXPLAIN 执行计划
4.1 核心字段
| 字段 |
读法 |
| id |
越大越先执行,相同则从上到下 |
| select_type |
出现 DERIVED 需警惕派生表 |
| type |
七档访问类型,见下表 |
| key / key_len |
实际用到的索引及参与的列数 |
| rows / filtered |
估算扫描量与过滤比例,越小越好 |
| Extra |
信息量最大,见 4.3 |
4.2 type 七档
system、const、eq_ref、ref、range、index、ALL,从左到右依次变差:
- 优秀:system、const、eq_ref
- 可接受:ref、range
- 必须优化:index、ALL
| 取值 |
判断 |
| Using index |
覆盖索引,好事 |
| Using index condition |
索引下推,好事 |
| Using filesort |
排序未走索引,需优化 |
| Using temporary |
用了临时表,需优化 |
| Using join buffer |
被驱动表缺索引 |
4.4 速查口诀
- type 从 const 到 ALL,越左越好,出现 ALL 必须优化。
- key 为 NULL 说明优化器没走索引。
- Extra 出现 filesort 或 temporary 需要优化。
4.5 进阶手段
- EXPLAIN ANALYZE(8.0.18+):看真实耗时与真实行数。
- EXPLAIN FOR CONNECTION <id>:分析正在运行的会话。
- SHOW WARNINGS:查看优化器改写后的语句。
- optimizer_trace:查看成本估算过程。
五、语句级剖析
5.1 SHOW PROFILE 用法
SET profiling = 1;
SHOW PROFILES; -- 列出各语句耗时
SHOW PROFILE FOR QUERY 1; -- 查看单条各阶段耗时
5.2 各阶段怎么读
- Sending data 占大头 → 扫描行数多或回表多。
- Sorting result → 排序耗时。
- converting HEAP to MyISAM → 临时表已落盘。
局限:8.0 已弃用,转向 performance_schema,由 events_statements_history_long 逐步替代。
六、索引优化
6.1 建索引四原则
- 选择性:区分度在 10% 以上才有价值。
- 覆盖性:尽量覆盖查询列,消除回表。
- 顺序:等值在前、范围在后、排序跟上。
- 克制:单表索引控制在 5 ~ 6 个。
6.2 修复索引失效
| 失效原因 |
修法 |
| 列上做函数运算 |
把运算移到常量一侧 |
| 隐式类型转换(如 varchar 未加引号) |
补引号,或对齐列类型 |
| OR 连接无索引列 |
拆成 UNION |
| 取反条件(!=、NOT IN) |
改为正向枚举 |
6.3 加索引前先算账
- 回表占比过大时,优化器宁可选全表扫描。
- 索引越多写越慢,且挤占 Buffer Pool。
6.4 索引维护
- ANALYZE TABLE 更新统计信息。
- FORCE INDEX 只作临时救急手段。
- 定期清理冗余索引与从未被使用的索引。
七、SQL 改写
7.1 深分页
- 延迟关联:子查询先用覆盖索引取主键,再回表取数据。
- 游标法:条件改为 id > 上一页末尾值,避免 OFFSET。
7.2 JOIN
- 被驱动表的关联列必须有索引。
- 小表驱动大表。
- 关联字段的字符集与类型保持一致(否则索引失效)。
7.3 子查询
- 8.0 已有半连接(semi-join)自动改写,一般不必手动转换。
- 确实慢时再改写为 JOIN。
7.4 通用写法
- 大事务拆批,每批约 1000 行。
- 批量插入按 500 ~ 1000 行一批。
- 只取需要的列,不用 SELECT *。
- 不对列包函数,让索引可用。
八、配置与架构层优化
8.1 参数配置
| 参数 |
建议 |
| innodb_buffer_pool_size |
给到物理内存 50% ~ 70% |
| redo 日志容量 |
4 ~ 8G,防止刷盘周期卡顿 |
| tmp_table_size |
调大,防止临时表落盘 |
8.2 架构手段(由轻到重)
读写分离 → 缓存挡热点 → 归档历史数据 → 分区表 → 分库分表(最后手段)。
8.3 MySQL 优化四方向
| 方向 |
内容 |
| 系统层 |
增加 CPU、内存,换 SSD,重点关注磁盘 IO |
| 数据库配置 |
调整内存、连接数、日志刷盘参数,让 MySQL 更合理地使用现有资源 |
| SQL 与索引 |
建立合适索引、优化 SQL、用 EXPLAIN 分析计划 |
| 架构与运维 |
主从复制、读写分离、高可用、备份恢复、监控告警 |
优化目标始终是三件事:查询更快、运行更稳、故障更少。
九、排查方法论
9.1 数据库突然变慢怎么办(应急响应)
- 看服务器资源:CPU、内存、磁盘是否异常。
- 看连接:是否连接数过多、打满连接池。
- 看 SQL:找出执行时间长的语句。
- 看执行计划:用 EXPLAIN 分析索引与调优情况。
- 优化:改 SQL、建索引、加缓存或调整架构。
一句话总结:发现问题靠监控,分析问题靠 EXPLAIN,解决问题靠索引、SQL、配置。
9.2 慢查询五步法
| 步骤 |
动作 |
| 第一步 |
拿到具体语句与耗时 |
| 第二步 |
EXPLAIN 看执行计划 |
| 第三步 |
对比预估 rows 与实际行数 |
| 第四步 |
定位问题类型:没索引 / 回表多 / 排序差 |
| 第五步 |
改写并验证,纳入监控防复发 |
9.3 现场辅助命令
SHOW PROCESSLIST; -- 查看正在执行的会话
SELECT * FROM performance_schema.data_lock_waits; -- 查看锁阻塞
SELECT * FROM information_schema.innodb_trx; -- 查看长事务
十、监控与预防
10.1 企业主要监控指标
| 指标 |
说明 |
| 连接数 |
最大连接数假设 1000,超过 800 报警 |
| QPS / TPS |
QPS = 每秒查询次数;TPS = 每秒完成的事务数(事务 = 一次完整操作流程) |
| 慢查询数 |
超过阈值需告警 |
| CPU / 内存 |
服务器资源是否够用 |
| 磁盘空间与 IO |
磁盘是否枯竭、IO 是否变慢 |
| 主从延迟 |
从库数据是否落后于主库 |
| 锁等待 |
是否有事务互相等待导致业务卡顿 |
10.2 上线前规范
- 开发约束:每表必须有主键。
- 新 SQL 上线前必须通过 EXPLAIN 评审。
- 大表变更先在预发环境压测。
10.3 上线后运营
- 慢查询数量设阈值告警。
- digest TopN 周报复盘。
- 优化完成后回归观察至少一个完整业务周期。
十一、典型案例
| 案例 |
现象 |
解法 |
| 深分页拖垮接口 |
LIMIT 100000, 20,每次扫十万行 |
延迟关联或游标法 |
| 隐式转换全表扫 |
varchar 列查询未加引号 |
类型对齐,或修改列类型 |
| 排序出现 filesort |
Extra 出现 Using filesort |
联合索引把排序列带上 |
| 优化器选错索引 |
rows 预估与实际相差十倍以上 |
ANALYZE TABLE,仍错则临时 FORCE INDEX |
附录、命令速查表
-- 采集
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW GLOBAL STATUS LIKE 'Slow_queries';
-- 分析
mysqldumpslow -s t -t 10 /path/to/slow.log
pt-query-digest /path/to/slow.log
-- Top SQL(8.0)
SELECT digest_text, count_star, ROUND(sum_timer_wait/1e12, 2) AS total_sec
FROM performance_schema.events_statements_summary_by_digest
ORDER BY sum_timer_wait DESC LIMIT 10;
-- 定位
EXPLAIN
EXPLAIN ANALYZE
SHOW PROCESSLIST;
SELECT * FROM information_schema.innodb_trx;
-- 维护
ANALYZE TABLE