生产SQL超时主因是WHERE未走索引致全表扫描,如函数操作、隐式转换;JOIN需驱动表限流;ORDER BY+LIMIT须覆盖索引;相关子查询应转LEFT JOIN;排查需结合EXPLAIN与慢日志Rows_examined。
生产里最常看到的超时,不是因为数据量大,而是 WHERE 里的字段根本没建索引,或者建了但写法绕过了索引。比如对索引列用函数:WHERE YEAR(create_time) = 2025,MySQL 就没法用 create_time 上的索引;又比如隐式类型转换:WHERE user_id = '123'(而 user_id 是 INT),也可能触发全表扫描。
EXPLAIN,重点关注 type 是否为 ALL 或 index,key 列是否为空 COLLATION 是 utf8mb4_bin 时 'abc' ≠ 'ABC') BETWEEN 或闭区间,避免 DATE(create_time) = '2025-01-01' 这类写法 JOIN 本身不危险,危险的是没控制驱动表顺序、没加过滤条件就直接连三张百万级表。比如 SELECT * FROM orders o JOIN users u ON o.user_id = u.id JOIN products p ON o.product_id = p.id,如果 orders 表没加 WHERE,MySQL 可能先扫全表 orders(100 万行),再对每行去联查 users 和 products,实际扫描行数轻松破亿。
FROM 后第一个表),且该表的 WHERE 条件必须能命中索引 SELECT * 在多表 JOIN 中出现,只取真正需要的字段,减少临时表和网络传输开销 ORDER BY created_at DESC LIMIT 20 看似简单,但如果 created_at 没索引,或有索引但 WHERE 条件导致无法复用该索引,MySQL 就得把所有匹配行捞出来再排序——哪怕最后只取 20 行,中间可能已排序 50 万行。
WHERE 条件字段 + ORDER BY 字段 +
SELECT 中的非主键字段(否则回表) LIMIT 10000, 20),即使有索引也慢,应改用游标分页(WHERE id )
ORDER BY 中混用 ASC/DESC(如 ORDER BY a ASC, b DESC)在 MySQL 8.0 之前无法用联合索引优化 SELECT id, (SELECT COUNT(*) FROM logs l WHERE l.order_id = o.id) cnt FROM orders o 这种写法,表面看简洁,实则是灾难:外层查 1 万条订单,内层就执行 1 万次 logs 表扫描。比显式 JOIN 还慢,且难以加索引优化。
LEFT JOIN + GROUP BY,例如上例可改写为 SELECT o.id, COUNT(l.order_id) cnt FROM orders o LEFT JOIN logs l ON o.id = l.order_id GROUP BY o.id EXISTS),确认内层 WHERE 是否能走索引;IN (subquery) 在 MySQL 中可能退化为物化临时表,不如 EXISTS 稳定 LATERAL,可安全表达依赖关系,但线上版本普遍不支持,慎用 实际排查时,光看 SQL 写法不够,得结合 SHOW PROCESSLIST 看状态(Sorting result、Creating sort index、Copying to tmp table 都是危险信号),以及慢日志里的 Rows_examined —— 它比执行时间更能暴露问题本质。