bigywater.tech
Flask · 85

Lazy Loading 与 Eager Loading

访问 author.books 的那一刻到底发生了什么?各种 lazy 策略发多少条 SQL?什么时候该用 eager loading?这篇用 SQL 事件把每种策略发出的语句一条一条数出来。

11 → 1
遍历 10 个作者的书:lazy=select 与 joined 的 SQL 条数
0 条
多对一的目标对象已在身份映射里时,访问它发出的 SQL 条数
raiseload
漏掉 eager 加载时立刻报 InvalidRequestError

默认的懒加载

实测 · 本机执行输出
  取出 1 个 Author:1 条 SQL
  第一次访问 a.books:新增 1 条 → SELECT book.id, book.title, book.author_id FROM book WHERE ? = book.author_id
  再访问 a.books 两次:新增 0 条
  多对一 b.author:0 条(Author 1 已经在这个 session 的身份映射里,直接取不发 SQL)
  清空 session 后再取 b.author:1 条 → SELECT author.id, author.name FROM author WHERE author.id = ?

上一篇:Model 与 Table 映射;下一篇:joinedload() 与 selectinload()。

各种 lazy 策略的 SQL 条数

实测 · 本机执行输出
  lazy=        SQL 条数   说明
  select       11       SELECT author.id, author.name FROM author
  joined       1        SELECT author.id, author.name, book_1.id AS id_1, book_1.title, book_1.author_id…
  subquery     2        SELECT author.id, author.name FROM author
  selectin     2        SELECT author.id, author.name FROM author
  immediate    11       SELECT author.id, author.name FROM author
  raise        1        访问 books → InvalidRequestError: 'Author.books' is not available due to lazy='raise'
  (noload 触发的警告: SADeprecationWarning: The ``noload`` loader strategy is deprecated and will be rem…)
  noload       1        访问 books → 访问到 0 本

options() 覆盖

实测 · 本机执行输出
  不加 options      11 条 SQL,遍历 books → 50 本
  joinedload       1 条 SQL,遍历 books → 50 本
  selectinload     2 条 SQL,遍历 books → 50 本
  subqueryload     2 条 SQL,遍历 books → 50 本
  raiseload        1 条 SQL,遍历 books → InvalidRequestError

detached 与 raiseload

实测 · 本机执行输出
  对象 detached 后读 a.books → DetachedInstanceError
  raiseload 下读 a.books → InvalidRequestError | 'Author.books' is not available due to lazy='raise'

lazy=dynamic

实测 · 本机执行输出
  a.books 的类型: AppenderQuery
  继续过滤 a.books.filter(...).all(): ['b0-1'] | 发了 1 条
  连续 count() 两次:发了 2 条(不缓存)

分步演示

Lazy 与 Eager Loading
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。Flask-SQLAlchemy 3.1.1、SQLite(内存库)
    • 没有验证:PostgreSQL 上的相同对比(第 87 篇的 N+1 会用 PG 量耗时)、lazy=write_only、大集合下 joined 与 selectin 的内存与耗时差别(第 86 篇)、多层嵌套关系的加载
    京ICP备2026067502号-1京公网安备11010502063487号