Skip to content

067. 选择预加载性能与 SQL 聚合顺序

学习目标

这一节继续比较关系预加载和选择预加载。

学完后,你应该能理解:

  • selectinload 为什么会拆成多条 SQL;
  • 大表场景为什么要避免过大的 join;
  • 用选择预加载筛选关联字段时要怎么绕;
  • SQL 聚合查询的执行顺序;
  • 为什么 WHERE 不能直接使用 SELECT 别名。

两种预加载的核心差异

joinedload 会把主表和关联表 join 成一条查询。

selectinload 会拆成多条查询。

常见过程是:

  1. 先查主表;
  2. 根据主表 ID 查关联表;
  3. 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 别名。

AI Agent 课程学习文档。