sql查询慢的主因是where条件写法不当、join导致结果集膨胀及select *滥用;如where中对字段用函数使索引失效,应改用范围查询;join需防一对多膨胀,可用count对比或窗口函数替代;分页宜用游标而非offset。

SQL 查询慢,不是因为没学过索引,而是因为没想清楚 WHERE 条件怎么写、JOIN 会不会放大结果集、SELECT * 会拖垮什么。
WHERE 子句里用函数导致索引失效
比如 WHERE YEAR(created_at) = 2023 看起来直观,但数据库没法用 created_at 上的索引——因为函数改变了字段原始值,优化器只能全表扫描。
- 改成范围查询:
WHERE created_at >= '2023-01-01' AND created_at - 如果必须按月查,优先建函数索引(postgresql 支持
CREATE INDEX ON t ((EXTRACT(YEAR FROM created_at)));mysql 8.0+ 支持函数索引,但需显式定义) - 避免在 WHERE 里对字段做运算:
WHERE price * 1.1 > 100同样走不了索引,应改为WHERE price > 100 / 1.1
JOIN 多张表时结果集意外膨胀
三张表 LEFT JOIN,本意是补全信息,结果行数翻了十几倍——大概率是某张表存在一对多关系,且没加聚合或去重逻辑。
- 先用
COUNT(*)和COUNT(DISTINCT t1.id)对比,确认是否重复膨胀 - 检查 JOIN 条件是否遗漏关键字段(比如只连了
user_id,但没限定tenant_id,跨租户数据混了) - 如果只是要关联最新一条记录,别用 JOIN,改用窗口函数:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY updated_at DESC)再过滤
ORDER BY + LIMIT 在大数据量下仍很慢
常见于分页场景:SELECT * FROM orders ORDER BY created_at DESC LIMIT 20 OFFSET 10000。OFFSET 越大,数据库越要先扫出前 10020 行再丢弃。
- 用游标分页替代 OFFSET:
WHERE created_at - 确保 ORDER BY 字段有索引,且和 WHERE 条件能复用同一索引(例如
WHERE status = 'paid' ORDER BY created_at,适合建联合索引(status, created_at)) - 如果排序字段有大量重复值(如
status只有 3 个取值),务必在 ORDER BY 末尾加上主键,避免排序不稳定:ORDER BY status, id
最常被忽略的点:执行计划不是看一眼 EXPLAIN 就完事,得盯住 rows 估算值是否接近真实扫描行数、type 是不是 range 或 ref、有没有出现 using filesort 或 Using temporary——这些才是真正卡顿的信号。