高效join需先过滤再关联,group by须覆盖非聚合列,count(*)优于count(字段),小表驱动大表并建对索引,数据分布不均时索引更关键。

JOIN前先过滤,别等关联完再WHERE
大表JOIN后才加WHERE条件,等于把几百万行全连完再筛,MySQL/PostgreSQL都会傻乎乎执行。真正高效的做法是:在JOIN子句里就用ON约束过滤,或对单表提前用WHERE缩小结果集。
- 错误写法:
SELECT * FROM orders JOIN users ON orders.user_id = users.id WHERE users.status = 'active'—— 先全量关联,再筛用户 - 正确写法:
SELECT * FROM orders JOIN (SELECT id FROM users WHERE status = 'active') AS users ON orders.user_id = users.id - 或者更稳的:
SELECT * FROM orders JOIN users ON orders.user_id = users.id AND users.status = 'active'(注意:这个AND必须写在ON里,不是WHERE)
GROUP BY字段必须覆盖SELECT中的非聚合列
MySQL 5.7+默认开启sql_mode=ONLY_FULL_GROUP_BY,一旦SELECT里出现没在GROUP BY中、又没套MAX()/SUM()等聚合函数的字段,直接报错:Expression #2 of SELECT list is not in GROUP BY clause。
- 典型翻车现场:
SELECT user_id, name, COUNT(*) FROM orders JOIN users ON orders.user_id = users.id GROUP BY user_id——name没参与分组也没聚合,必报错 - 解法一(推荐):补全
GROUP BY:GROUP BY user_id, name - 解法二:用聚合取确定值,比如
MAX(name)(前提是每个user_id对应唯一name) - 别关
ONLY_FULL_GROUP_BY——关了只是掩盖问题,可能返回不可靠的name
COUNT(*) vs COUNT(字段)性能差得远
跨表汇总时,COUNT(*)走主键索引最快;但写成COUNT(orders.amount)或COUNT(users.email),数据库就得逐行判断该字段是否为NULL,IO和CPU开销直线上升。
- 要统计“有多少订单”,无条件用
COUNT(*) - 要统计“有多少订单金额不为空”,才用
COUNT(orders.amount),且确保amount字段有索引(否则更慢) - 如果JOIN后有
LEFT JOIN,COUNT(*)会把右表为NULL的行也计进去,这时想算“有用户信息的订单数”,得用COUNT(users.id)
小表驱动大表 + 建对索引,比调优SQL本身管用十倍
优化器选错驱动表,或者关键字段没索引,再短的SQL也会变慢。别迷信EXPLAIN里的type=ref,重点看rows是不是真实扫描行数、key有没有命中预期索引。
-
orders表千万级,users表十万级 → 让users当驱动表(FROM users JOIN orders),避免对orders全表扫描 - JOIN字段必须有索引:比如
orders.user_id要有索引,users.id是主键自然有,但users.status如果常用来过滤,就得建(status, id)联合索引 - GROUP BY字段如果有复合条件,比如
GROUP BY DATE(created_at), user_type,考虑建(created_at, user_type)前缀索引
最常被跳过的其实是数据分布——如果users.status = 'active'只占0.1%,但没索引,优化器宁可全表扫也不走索引。这时候看EXPLAIN的rows和实际COUNT(*)一比,立刻明白问题不在SQL写法,而在数据和索引是否匹配。










