vault backup: 2026-05-21 12:20:54

This commit is contained in:
hhs
2026-05-21 12:20:54 +08:00
parent 2e84683d9c
commit 531d4b7d8c
8 changed files with 867 additions and 89 deletions
@@ -36,10 +36,13 @@ graph BT
```sql
-- 用法 1:在 SELECT 中调用
SELECT
SELECT
username,
(SELECT COUNT(*) FROM orders WHERE user_id = users.id) AS order_count
FROM users;
-- 副作用:这个查询其实也是"相关子查询"的典型例子
-- 因为内层的 user_id = users.id 引用了外层表的列
-- 每一行 users 的记录都会触发一次内层查询
-- 用法 2:在 WHERE 中等价于常量
SELECT * FROM products
@@ -50,6 +53,10 @@ INSERT INTO reports (month, order_total)
VALUES ('2026-05', (SELECT SUM(amount) FROM orders WHERE MONTH(created_at) = 5));
```
> [!QUESTION] 上面用法 1 的子查询,每一行都要重新执行吗?
> 是的——但不是因为"放在 SELECT 里",而是因为它引用了外层的 `users.id`。
> 这就引出了子查询最重要的分类维度:**相关 vs 无关**。
### 相关 vs 无关子查询
这是理解子查询性能的基石:**子查询是否引用了外层查询的列?**
@@ -436,6 +443,14 @@ WITH RECURSIVE org_chain AS (
SELECT * FROM org_chain ORDER BY level, name;
```
> [!QUESTION] 为什么叫「自我引用」?递归 CTE 到底怎么执行的?
> `org_chain` 这个名字在定义内部就被引用了——`FROM employees e INNER JOIN org_chain oc`。
> 第一轮:只执行锚点成员,得到根节点(CEO);
> 第二轮:用第一轮的结果去 JOIN employees,得到 CEO 的直接下属;
> 第三轮:用第二轮的结果去 JOIN,得到下属的下属……
> 直到某一轮 JOIN 结果为空,递归自动终止。
> **关键:没有终止条件 → 无限循环 → 触发 `cte_max_recursion_depth` 报错。**
> [!WARNING] 递归 CTE 的注意事项
> - MySQL 默认递归深度上限为 **1000**(`cte_max_recursion_depth`),超出会报错
> - 务必设置合理的终止条件,否则会无限递归直到达到上限
@@ -466,6 +481,16 @@ SELECT * FROM org_chain ORDER BY level, name;
> **子查询不是万能的——它首先是为了表达清晰,其次才是性能。**
> 写完后务必跑 `EXPLAIN`,确认没有意料之外的临时表或多表扫描。当数据量上去后,能改写成 JOIN 的子查询就尽量改写,因为 JOIN 的执行路径对 Optimizer 更加透明。
> [!TIP] 学习路径建议
> 如果你刚接触子查询,建议按这个顺序学习:
> 1. **标量子查询** → 先理解「子查询就是一次独立的 SELECT」
> 2. **相关 vs 无关** → 理解执行次数差异(本文核心概念)
> 3. **IN vs EXISTS** → 掌握两种过滤范式,重点理解 NOT IN 陷阱
> 4. **派生表** → 子查询作为虚拟表,理解物化与 Flattening
> 5. **CTE** → 用现代语法重写派生表,最终挑战递归 CTE
>
> 每一步都要跑 `EXPLAIN` 看执行计划——**看懂 EXPLAIN 比背语法重要十倍**。
## 关联笔记
- [[hhs/MySQL/02-SQL核心/08-DQL SELECT 全解析]] — SELECT 执行顺序与子查询的关系