索引非万能,函数、隐式转换、or等致失效;order by不匹配索引触发filesort;join缺索引易笛卡尔积;线上加索引需algorithm=inplace或pt-osc避锁表。

为什么加了索引查询还是慢
索引不是万能的,WHERE 条件中用了函数、类型隐式转换、或 OR 连接多个非索引字段,都会导致索引失效。比如 WHERE YEAR(create_time) = 2023,MySQL 无法用 create_time 上的索引;又如 WHERE user_id = '123'(user_id 是 INT),会触发隐式转换,全表扫描。
实操建议:
- 用
EXPLAIN看type是否为ref/range,避免ALL;关注key和possible_keys是否命中预期索引 - 把函数移到等号右侧:把
YEAR(create_time) = 2023改成create_time BETWEEN '2023-01-01' AND '2023-12-31' - 确保比较字段类型一致:
user_id = 123(而非字符串'123') - 多个
OR条件尽量改写为UNION,尤其当各分支可走不同索引时
ORDER BY 为什么会触发 filesort
当 ORDER BY 字段不在索引最左前缀,或索引包含 WHERE 条件但顺序不匹配时,MySQL 就得额外排序,即 Extra: Using filesort。这不是真写磁盘文件,但意味着不能利用索引有序性,性能下降明显。
实操建议:
- 复合索引要兼顾
WHERE和ORDER BY:例如查询SELECT * FROM order WHERE status = 'paid' ORDER BY created_at DESC,建索引应为(status, created_at),而非单独created_at - 避免
SELECT *配合大字段(如TEXT),否则即使走了索引,回表代价也高;考虑覆盖索引:把常用查询字段加入索引末尾,如(status, created_at, user_id, amount) -
ORDER BY ... DESC在 MySQL 8.0+ 支持降序索引,但 5.7 及以前默认升序,DESC不生效,需确认版本
JOIN 多张表时怎么避免笛卡尔积和慢查询
没写 ON 条件、或 ON 字段无索引,极易引发行数爆炸。比如两张各 10 万行的表没关联条件,结果就是 100 亿行中间集 —— 即使最终 LIMIT 10,MySQL 仍可能先算完再截断。
实操建议:
- 所有
JOIN必须有明确、带索引的ON条件;检查EXPLAIN中各表的rows是否合理,特别警惕驱动表(第一行)rows过大 - 小表驱动大表:让过滤后结果集更小的表做驱动表(
STRAIGHT_JOIN可强制顺序,但慎用) - 避免在
ON或WHERE中对关联字段做函数操作,例如ON UPPER(a.name) = UPPER(b.name)会让索引失效 - 临时表场景下,先用
CREATE TEMPORARY TABLE+ 索引预处理中间结果,比直接多层嵌套JOIN更可控
如何安全地优化线上慢查询而不锁表
在大表上 ALTER TABLE 加索引,默认会锁表(尤其 MySQL 5.6 之前),而 pt-online-schema-change 或 MySQL 8.0+ 的 ALGORITHM=INPLACE 能规避这个问题,但仍有风险点。
实操建议:
- 优先用
ALTER TABLE ... ADD INDEX idx_name ON (col) ALGORITHM=INPLACE, LOCK=NONE(需引擎支持,InnoDB 一般 OK);执行前查INFORMATION_SCHEMA.INNODB_TRX确认无长事务 - 用
pt-online-schema-change时,务必加--dry-run和--test-db先验证;注意它会创建触发器,高并发写入下可能拖慢源表 - 慢查询优化后,用
SLOW_LOG和performance_schema.events_statements_summary_by_digest对比前后avg_timer_wait和exec_count - 别只盯着单条 SQL:有时瓶颈在应用层——比如循环里查数据库(N+1 问题),这种必须改代码,加索引没用
真正卡住的往往不是语法或索引本身,而是 WHERE 条件里的业务逻辑歧义、时间范围误设、或 JOIN 后未及时 GROUP BY 导致聚合膨胀。上线前拿真实数据量 + 线上执行计划复现,比任何理论都管用。










