定位慢查询:确认问题 SQL 与性能瓶颈
定位慢查询是优化的第一步,切忌盲目加索引。首先需开启慢查询日志,通过执行 SET GLOBAL slow_query_log = ON; 与 SET GLOBAL long_query_time = 1; 记录执行超时的 SQL。结合 mysqldumpslow 或 pt-query-digest 工具可快速聚合高频慢 SQL 并提取完整参数。定位时需严格区分性能瓶颈来源:若 Lock_time 远大于 Query_time,说明是行锁或表锁等待而非引擎执行慢;若数据库返回极快但应用层响应延迟,通常是网络抖动或代码序列化耗时。借助 Performance Schema 的 events_statements_history 表或 Prometheus+Grafana 监控面板,可直观对比 Rows_examined 与 Rows_sent 比例。只有明确是数据库层面的扫描或计算瓶颈,才进入后续执行计划分析阶段。

看懂 EXPLAIN:从执行计划判断索引是否有效
EXPLAIN 是解读 SQL 执行计划的核心工具。执行 EXPLAIN SELECT ... 后,需重点关注 type、key、rows 与 Extra 字段。type 反映访问类型,从优到劣依次为 system、const、eq_ref、ref、range、index、ALL,出现 ALL 即代表全表扫描。key 显示优化器实际选用的索引,若为 NULL 则未命中。rows 为预估扫描行数,数值过大通常意味着索引失效或统计信息陈旧。Extra 包含关键诊断信号:Using filesort 表示无法利用索引完成排序,需额外内存或磁盘排序;Using temporary 说明使用了临时表处理 GROUP BY 或 DISTINCT,极易引发 IO 瓶颈。结合 filtered 字段可评估索引过滤效率,若 rows * filtered 仍接近全表数据量,则当前索引设计存在明显缺陷。

索引优化:围绕查询条件设计合适索引
索引设计必须紧密贴合查询模式。单列索引适用于独立等值查询或排序场景,而联合索引需严格遵循“最左前缀匹配原则”。例如索引 (status, create_time, user_id),查询条件必须包含 status 才能生效,若仅查 create_time 则索引失效。设计时应将等值匹配列置于最左,范围查询列居中,排序/分组列靠右,以最大化利用索引树。当查询所需字段全部包含在索引中时,会触发覆盖索引(Extra 显示 Using index),彻底避免回表开销。同时,需定期通过 SHOW INDEX FROM table_name 审查冗余索引,如已存在 (a, b) 则无需单独创建 (a)。避免为低频查询或高并发写入表盲目堆砌索引,以免拖慢 INSERT/UPDATE 性能。
优化后验证:对比执行计划与实际耗时
优化效果绝不能仅凭 EXPLAIN 的理论预估下定论,必须结合真实运行数据验证。MySQL 8.0 引入的 EXPLAIN ANALYZE 可直接输出各执行节点的实际耗时与真实行数,精准定位耗时瓶颈。对于旧版本,可开启 SET profiling = 1; 执行查询后通过 SHOW PROFILES; 查看精确耗时。验证时需对比优化前后的核心指标:Rows_examined 是否显著下降、duration 是否缩短、CPU 与 IO 使用率是否回落。若执行计划显示 type 从 ALL 变为 ref 或 range,且实际耗时降低 50% 以上,方可确认优化有效。建议在预发环境使用生产脱敏数据压测,避免 Buffer Pool 缓存干扰,确保优化方案在真实负载下具备稳定性与可复用性。

常见避坑:索引不是越多越好
索引滥用是性能恶化的常见诱因。隐式类型转换会导致索引失效,如 WHERE phone = 13800000000(phone 为 VARCHAR)会触发隐式转换并走全表扫描;对索引列使用函数(如 WHERE YEAR(create_time)=2023)或前导模糊匹配(LIKE '%abc')同样会破坏索引结构。低选择性字段(如性别、状态枚举)建立索引收益极低,反而增加 B+ 树维护成本。联合索引顺序颠倒(将范围查询列放最左)会阻断后续列的匹配。此外,SELECT * 会破坏覆盖索引,强制回表;统计信息过期会导致优化器选错索引,需定期执行 ANALYZE TABLE 刷新。当表数据量极小(如不足千行)或写入远大于读取时,全表扫描往往比索引查找更快,此时不应强行加索引。
