高 困难
MySQL执行计划与主从延迟实战#
一句话答案#
EXPLAIN 看执行计划重点关注 type(ALL 全表扫描必须优化)、key(实际用了什么索引)、rows(扫描行数)、Extra(Using filesort/temporary 是性能杀手);主从延迟核心看
Seconds_Behind_Master,常见原因是大事务、DDL、从库性能不足。
核心要点
一、EXPLAIN 字段逐项解读
| 字段 | 含义 | 关注点 |
|---|---|---|
| type | 访问类型 | ALL<index<range<ref<eq_ref<const,ALL 必须优化 |
| possible_keys | 可能用的索引 | 有但没用=索引失效 |
| key | 实际用的索引 | NULL=全表扫描 |
| key_len | 索引使用长度 | 联合索引是否用满 |
| rows | 预估扫描行数 | 越小越好 |
| filtered | 过滤百分比 | 100%=无额外过滤 |
| Extra | 额外信息 | filesort/temporary/index condition pushdown |
type 从差到好排序:
ALL → index → range → index_merge → ref → eq_ref → const → system
全表 全索引 范围 索引合并 非唯一索引 唯一索引 主键常量plaintextExtra 关键词:
| Extra 值 | 含义 | 建议 |
|---|---|---|
| Using filesort | 无法用索引排序,额外排序 | 建索引覆盖 ORDER BY |
| Using temporary | 需要临时表(GROUP BY/DISTINCT) | 优化查询或加索引 |
| Using index | 覆盖索引,不回表 | 最优 |
| Using index condition | 索引条件下推(ICP) | 正常优化 |
| Using where | Server 层过滤 | 看能否下推到索引层 |
二、真实慢 SQL 案例
案例1:联合索引最左前缀失效
-- 索引: idx_status_created (status, created_at)
-- 慢SQL:
SELECT * FROM orders WHERE created_at > '2026-01-01';
-- EXPLAIN: type=ALL, key=NULL (跳过了status,最左前缀失效)
-- 修复: 加上status条件或单独建created_at索引
SELECT * FROM orders WHERE status IN (1,2,3) AND created_at > '2026-01-01';
-- EXPLAIN: type=range, key=idx_status_createdsql案例2:隐式类型转换导致索引失效
-- 索引: idx_phone (phone VARCHAR(20))
-- 慢SQL:
SELECT * FROM users WHERE phone = 13800138000; -- 数字比较
-- EXPLAIN: type=ALL (隐式转换VARCHAR→数字,索引失效)
-- 修复: 字符串类型传字符串
SELECT * FROM users WHERE phone = '13800138000';
-- EXPLAIN: type=ref, key=idx_phonesql案例3:回表代价过高
-- 索引: idx_user_id (user_id)
-- 慢SQL: 查询SELECT *返回大量字段
SELECT * FROM orders WHERE user_id = 100 ORDER BY created_at DESC LIMIT 100;
-- EXPLAIN: type=ref, rows=50000, Extra=Using filesort
-- 问题: 回表5万行后再排序
-- 修复: 建联合索引覆盖查询+排序
ALTER TABLE orders ADD INDEX idx_user_created(user_id, created_at);
-- EXPLAIN: type=ref, rows=100, Extra=Using index (覆盖索引+索引排序)sql三、主从延迟排查
监控命令:
SHOW SLAVE STATUS\G
-- 关键字段:
-- Seconds_Behind_Master: 延迟秒数(0=同步,NULL=复制断开)
-- Relay_Master_Log_File + Exec_Master_Log_Pos: 当前回放位置
-- Last_SQL_Error: 最近SQL线程错误sql延迟常见原因及解决:
| 原因 | 特征 | 解决 |
|---|---|---|
| 大事务 | 主库一个事务binlog很大(如批量UPDATE百万行) | 拆分大事务为小批次 |
| DDL | ALTER TABLE 锁表期间从库阻塞 | 用 pt-osc/gh-ost 在线DDL |
| 从库性能差 | 从库机器配置低 | 升配/SSD/多从库分担 |
| 单线程回放 | MySQL 5.6前只有一个SQL线程 | 升级到5.7+并行复制 |
| 大量写入 | 主库写入 QPS 突增 | 增加从库/分库分表 |
| 锁等待 | 从库有长查询阻塞回放 | 排查从库慢查询 |
四、读写分离一致性保障
| 方案 | 原理 | 适用场景 |
|---|---|---|
| 强制走主库 | 写后读直接查主库 | 写后立即读(如下单后查订单) |
| 等待 GTID | 写后记录GTID,读从库时等该GTID回放完 | 要求一致但可接受短等待 |
| 中间件路由 | 按业务场景自动路由主/从 | ProxySQL/ShardingSphere |
| 缓存兜底 | 写后把结果写缓存,读优先走缓存 | 高频读场景 |
五、执行计划优化 checklist
□ type 是否为 ALL/index?→ 加索引或优化查询条件
□ key 是否为 NULL?→ 检查索引是否失效(类型转换/函数/OR)
□ rows 是否过大?→ 缩小范围或加更精确索引
□ Extra 有 filesort?→ 索引覆盖 ORDER BY 字段
□ Extra 有 temporary?→ 优化 GROUP BY 或加索引
□ key_len 联合索引是否用满?→ 检查最左前缀
□ filtered 太低?→ 索引选择性差,考虑换索引plaintext面试回答(2分钟版)
看执行计划我重点关注四个字段。type 表示访问类型,ALL 全表扫描必须优化,至少要到 range 或 ref 级别。key 看实际用了什么索引,NULL 说明索引失效需要排查原因——常见的是最左前缀不满足、隐式类型转换、对索引列用了函数。rows 是预估扫描行数,配合 filtered 判断索引选择性。Extra 里 Using filesort 和 Using temporary 是性能杀手,前者说明排序没走索引,解决方案是建联合索引覆盖 ORDER BY。主从延迟我通过 SHOW SLAVE STATUS 的 Seconds_Behind_Master 监控,常见原因是大事务、在线 DDL、从库单线程回放。大事务要拆批次,DDL 用 gh-ost,回放瓶颈升级 MySQL 5.7+ 并行复制。读写分离的一致性保障方面,写后读场景强制走主库,或者用 GTID 等待机制确保从库已回放到位。
追问与易错
追问方向:
- “索引失效的常见场景?”→ 隐式类型转换、对索引列用函数或计算、LIKE 左模糊、OR 连接非索引列、!= / NOT IN 否定条件、IS NOT NULL 看优化器判断;本质是让 B+ 树无法按序定位
- “EXPLAIN 的 rows 准确吗?”→ 不准确,是基于索引统计信息的估算值;可通过 ANALYZE TABLE 刷新统计信息提高准确度,但仍非精确值
- “怎么监控主从延迟?”→ SHOW SLAVE STATUS 的 Seconds_Behind_Master 是基础指标;pt-heartbeat 通过写入时间戳更精确;生产环境配合 Prometheus + Grafana 设置告警阈值
- “并行复制怎么配?”→ MySQL 5.7+ 设置 slave_parallel_workers=4~16、slave_parallel_type=LOGICAL_CLOCK,按组提交的事务并行回放,大幅降低从库延迟
易错点:
- ❌ “type=index 就是好的”——index 是全索引扫描,只是不回表而已,仍然很慢
- ❌ “Seconds_Behind_Master=0 就没延迟”——网络断开时也显示0,要结合 IO/SQL thread 状态判断
- ❌ “加了索引就一定会用”——优化器可能认为全表扫描更快(小表/选择性低)
- ✅ 看执行计划的口诀:type→key→rows→Extra,四步定位问题