切换日光/暗黑模式
067. 选择预加载性能与 SQL 聚合顺序
学习目标
这一节继续比较关系预加载和选择预加载。
学完后,你应该能理解:
selectinload为什么会拆成多条 SQL;- 大表场景为什么要避免过大的 join;
- 用选择预加载筛选关联字段时要怎么绕;
- SQL 聚合查询的执行顺序;
- 为什么
WHERE不能直接使用SELECT别名。
两种预加载的核心差异
joinedload 会把主表和关联表 join 成一条查询。
selectinload 会拆成多条查询。
常见过程是:
- 先查主表;
- 根据主表 ID 查关联表;
- ORM 再把关联数据组装回主对象。
业务层拿到的对象可能差不多。
但数据库实际执行方式完全不同。
大表场景的风险
一条很大的 join 查询会带来几个问题:
- 字段数量变多;
- 一对多关系导致行数膨胀;
- 数据库临时结果变大;
- 查询慢;
- 内存占用高。
数据量小时问题不明显。
数据量上来以后,大 join 很容易成为性能瓶颈。
这也是很多后端团队偏好显式 SQL 的原因。
选择预加载的优势
选择预加载把查询拆开。
主表先稳定查出来。
关联表再按主表 ID 批量查询。
这能减少一条 SQL 里 join 过深带来的复杂度。
对只展示关联数据、不按关联字段排序筛选的接口来说,它通常更合适。
选择预加载的限制
如果接口需要按关联表字段筛选或排序,选择预加载就不如 join 直接。
例如要查负责人姓名符合条件的项目。
用 joinedload 或普通 join 时,关联字段就在同一条 SQL 里。
用 selectinload 时,主表查询阶段还没有负责人详情。
这时通常要改成两步:
- 先查用户表,找出符合条件的用户 ID;
- 再用这些 ID 过滤项目表。
这个写法更绕,但能避免大 join。
聚合统计查询
项目费用统计更适合用 SQL 聚合。
核心是:
- 项目表作为主表;
- 审批单表按项目 ID 关联;
- 只统计审批通过的金额;
- 使用
SUM计算已花费; - 用预算减已花费得到余额。
这种统计在数据库里完成,比把审批单全部查回 Python 再算更适合分页和排序。
SQL 执行顺序
理解 SQL 执行顺序很重要。
大致顺序可以这样记:
txt
FROM / JOIN / ON
WHERE
GROUP BY
聚合函数
HAVING
SELECT
ORDER BY
LIMIT这个顺序解释了很多看起来奇怪的问题。
WHERE 和别名
SELECT 里的别名是在后面阶段才产生的。
所以 WHERE 不能直接使用 SELECT 里定义的别名。
例如:
sql
SELECT budget - spend AS balance
FROM project
WHERE balance > 0这类写法通常不成立。
因为执行 WHERE 时,balance 还不存在。
ORDER BY 可以用别名
ORDER BY 执行得更靠后。
这时 SELECT 别名已经出现。
所以排序经常可以这样写:
sql
ORDER BY balance DESC如果筛选聚合结果,通常使用 HAVING。
如果筛选普通字段,通常使用 WHERE。
ORM 不是越多越好
ORM 能减少很多重复代码。
但复杂查询、统计查询、排序筛选经常需要回到 SQL。
对于 JS 开发者来说,可以把 ORM 理解成对象化的数据访问工具。
它不是 SQL 的替代品。
越接近报表、统计、复杂筛选,就越需要读懂 SQL。
这一节的重点
这一节重点是选择查询方式。
你需要记住:
joinedload是一条 join 查询;selectinload是多条批量查询;- 大表场景要警惕 join 过大;
- 按关联字段筛选时,选择预加载可能要先查关联表 ID;
- 统计、分页、排序更适合交给 SQL;
WHERE不能用后面才生成的SELECT别名。