1061 字
约 3 分钟
6
S11-B03 · 查询:WHERE / JOIN / GROUP BY / 子查询 / 分页
2024-12-31
2026-11-07

适用读者:能写出结果但不确定"为什么这么写"的人。
前置:S11-B02。
实测环境:MySQL 8.0.46(s11lab,20,000 行,2026-11-05)。
覆盖旧文:2598(296) 1734(168) 1733(93) 1218(85) 1221(70) 1225(66) 1228(54) 1226(42) 1224(28) 1222(52) 1736(149) 844(70) 2594(479) 1227(79) 1223(79) = 1,810 点。
覆盖扩展:E-S11-004(书写顺序 vs 执行顺序)。
上一篇:S11-B02 · 建表:数据类型、字符集与范式
下一篇:S11-B04 · 增删改与表管理。


1. 一句话结论

SQL 的书写顺序是 SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT,但执行顺序是 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。

这条顺序解释了三件事:

现象 原因
WHERE 里不能直接用 SELECT 的别名 WHERE 早于 SELECT
HAVING 可以用聚合结果,WHERE 不行 GROUP BY 早于 HAVING
ORDER BY 可以用别名 ORDER BY 晚于 SELECT

2. 五种 LIKE 与通配符

写法 含义
like 'abc%' 前缀匹配(可走索引)
like '%abc' 后缀匹配(不可走索引范围)
like '%abc%' 包含(索引失效,J02)
like 'a_c' _ 匹配单字符
连续 % %% 与 % 等价(MySQL 会规整)

实测(J02 会展开):name like 'u1%' → type=range;name like '%u1%' → type=index(全索引扫描)——都不是"完全不用索引",但代价差很多。


3. 聚合与分组

select city, count(*) cnt, avg(age) from t_user group by city having cnt > 500 order by cnt desc limit 5;
子句 作用 注意
GROUP BY 分组 非聚合列必须出现在 group by 中(ONLY_FULL_GROUP_BY)
HAVING 分组后过滤 能用 WHERE 就别用 HAVING(后者无法用索引)
count(*) / count(1) / count(列) 计数 count(列) 不计 NULL(A-844)

判据:"这个条件能提前到分组前吗?" 能 → WHERE(更快)。


4. JOIN 与子查询

类型 说明
INNER JOIN 只保留匹配
LEFT JOIN 左表全保留
子查询(IN/EXISTS) 语义清晰,优化器可能改写成 JOIN
派生表 from (select ...) t

实践判据:

需要"过滤后关联" → 子查询/CTE(可读性好)
需要"关联后过滤" → JOIN + WHERE
数据量大 → 看 EXPLAIN 再决定(优化器常会重写)

5. 分页:LIMIT 与深翻

select id,name from t_user order by id limit 10 offset 10;      -- 正常
select id,name from t_user order by id limit 10 offset 19990;   -- 深翻

实测(20,000 行):

offset 10    → 29 ms
offset 19990 → 54 ms
观察 说明
深翻确实更慢 但要扫过前 19,990 行
差距不是"数量级" 2 万行规模下只是约 1.9×
数据量上去后会放大 百万行时 offset 越大越糟

判据:"用户会翻到很深的页吗?" 不会 → limit offset 够用;会 → 游标分页(S08-J01)。


6. UNION 与 UNION ALL

写法 去重 性能
UNION ✅(隐含排序) 慢
UNION ALL ❌ 快

判据:"两个结果集可能有重复吗?" 不可能 → 用 UNION ALL(省一次去重)。


7. 五个误区

误区 真相
"WHERE 能用 SELECT 别名" 不能(执行顺序)
"HAVING 和 WHERE 一样" HAVING 在分组后,无法用索引
"count(列) 等于 count(*)" count(列) 不计 NULL
"UNION 总是更好" 有去重开销,能确定不重复就用 UNION ALL
"深翻只是慢一点" 数据量大时是线性恶化

8. 自检

  1. 写出执行顺序(七个阶段)。
  2. 为什么 WHERE 不能用 SELECT 别名?
  3. 三种 LIKE 写法各自能否用索引?
  4. count(列) 与 count(*) 的差别?
  5. UNION 与 UNION ALL 怎么选?

9. 下一篇

S11-B04 · 增删改与表管理

查询讲完,下一篇讲写入侧:truncate/delete/drop 的三层差别,以及 ALTER 的代价。


本篇属于系列《S11 · 数据库与 SQL》。旧文来源:A-2598/1734/1733/1218/1221/1225/1228/1226/1224/1222/1736/844/2594/1227/1223;扩展点 E-S11-004。深翻计时为 MySQL 8.0.46 实测(2 万行)。

S11-B03 · 查询:WHERE / JOIN / GROUP BY / 子查询 / 分页
https://www.clxhxhhr.top/posts/5327/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。