SQL 生产环境最常见的 10 个导致查询超时的写法错误

9次阅读

生产sql超时主因是WHERE未走索引致全表扫描,如函数操作、隐式转换;JOIN需驱动表限流;ORDER BY+LIMIT须覆盖索引;相关子查询应转LEFT JOIN;排查需结合EXPLaiN与慢日志Rows_examined。

SQL 生产环境最常见的 10 个导致查询超时的写法错误

WHERE 条件没走索引,全表扫描卡死连接

生产里最常看到的超时,不是因为数据量大,而是 WHERE 里的字段根本没建索引,或者建了但写法绕过了索引。比如对索引列用函数:WHERE YEAR(create_time) = 2024mysql 就没法用 create_time 上的索引;又比如隐式类型转换WHERE user_id = '123'(而 user_idint),也可能触发全表扫描。

  • 检查执行计划必须用 EXPLAIN,重点关注 type 是否为 ALLindexkey 列是否为空
  • 字符串字段做等值查询,注意前后空格和大小写(如 COLLATIONutf8mb4_bin'abc''ABC'
  • 时间范围查询优先用 BETWEEN 或闭区间,避免 date(create_time) = '2024-01-01' 这类写法

JOIN 多张大表没加驱动表限制,笛卡尔积爆炸

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 中出现,只取真正需要的字段,减少临时表和网络传输开销
  • 如果必须关联多张大表,考虑先用子查询或 CTE 把驱动表结果集收窄(但注意 MySQL 8.0 以前 CTE 不支持下推条件)

ORDER BY + LIMIT 混用却没覆盖索引,排序成本飙升

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 之前无法用联合索引优化

子查询写成相关子查询,N+1 查询变 N×M

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 稳定
  • MySQL 8.0.23+ 支持 LATERAL,可安全表达依赖关系,但线上版本普遍不支持,慎用

实际排查时,光看 SQL 写法不够,得结合 SHOW PROCEsslIST 看状态(sorting resultCreating sort indexCopying to tmp table 都是危险信号),以及慢日志里的 Rows_examined —— 它比执行时间更能暴露问题本质。

text=ZqhQzanResources