SqlAlchemy 2.0 中文文档(十八)(2)https://developer.aliyun.com/article/1562921
使用列推迟限制加载的列
列推迟是指在查询该类型的对象时,ORM 映射的列在 SELECT 语句中被省略的列。 这里的一般原因是性能,在表具有很少使用的列且具有潜在的大数据值的情况下,完全在每次查询时加载这些列可能会耗费时间和/或内存。 SQLAlchemy ORM 提供了多种控制加载列的方式。
本节中的大多数示例都是ORM 加载器选项的示例。 这些是小型构造,传递给 Select.options()
方法的 Select
对象,然后在对象编译为 SQL 字符串时由 ORM 消耗。
使用 load_only()
来减少加载的列
load_only()
加载器选项是在已知只会访问少量列的对象时使用的最快捷的选项。此选项接受一个可变数量的类绑定属性对象,指示应加载的那些列映射属性,其中除主键外的所有其他列映射属性将不会成为被获取的列的一部分。在下面的示例中,Book
类包含列 .title
、.summary
和 .cover_photo
。使用 load_only()
,我们可以指示 ORM 仅预先加载 .title
和 .summary
列:
>>> from sqlalchemy import select >>> from sqlalchemy.orm import load_only >>> stmt = select(Book).options(load_only(Book.title, Book.summary)) >>> books = session.scalars(stmt).all() SELECT book.id, book.title, book.summary FROM book [...] () >>> for book in books: ... print(f"{book.title} {book.summary}") 100 Years of Krabby Patties some long summary Sea Catch 22 another long summary The Sea Grapes of Wrath yet another summary A Nut Like No Other some long summary Geodesic Domes: A Retrospective another long summary Rocketry for Squirrels yet another summary
在上面的例子中,SELECT 语句省略了 .cover_photo
列,并且仅包含了 .title
和 .summary
列,以及主键列 .id
;ORM 通常会始终获取主键列,因为这些列是必需的,用于建立行的标识。
一旦加载,对象通常会对其余未加载的属性应用惰性加载行为,这意味着当首次访问任何属性时,将在当前事务中发出 SQL 语句以加载该值。下面,访问 .cover_photo
会发出一个 SELECT 语句来加载其值:
>>> img_data = books[0].cover_photo SELECT book.cover_photo AS book_cover_photo FROM book WHERE book.id = ? [...] (1,)
惰性加载始终使用对象处于持久状态的Session
发出。如果对象已分离于任何Session
,操作将失败,引发异常。
作为在访问时惰性加载的替代方案,还可以配置延迟列在访问时引发一个信息性异常,而不考虑它们的附加状态。当使用 load_only()
构造时,可以使用 load_only.raiseload
参数来指示这一点。有关背景和示例,请参见使用 raiseload 防止延迟列加载部分。
提示
如其他地方所述,在使用异步 I/O (asyncio)时,不可用惰性加载。
使用 load_only()
与多个实体
load_only()
限制自身仅针对其属性列表中引用的单个实体(目前不允许传递跨越多个实体的属性列表)。在下面的示例中,给定的 load_only()
选项仅适用于 Book
实体。也选择的 User
实体不受影响;在生成的 SELECT 语句中,user_account
的所有列都存在,而 book
表只有 book.id
和 book.title
:
>>> stmt = select(User, Book).join_from(User, Book).options(load_only(Book.title)) >>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname, book.id AS id_1, book.title FROM user_account JOIN book ON user_account.id = book.owner_id
如果我们想要将 load_only()
选项应用于 User
和 Book
,我们将使用两个单独的选项:
>>> stmt = ( ... select(User, Book) ... .join_from(User, Book) ... .options(load_only(User.name), load_only(Book.title)) ... ) >>> print(stmt) SELECT user_account.id, user_account.name, book.id AS id_1, book.title FROM user_account JOIN book ON user_account.id = book.owner_id
在相关对象和集合上使用 load_only()
当使用关系加载器来控制相关对象的加载时,任何关系加载器的 Load.load_only()
方法都可以用于将 load_only()
规则应用于子实体的列。在下面的示例中,使用 selectinload()
来加载每个 User
对象上的相关 books
集合。通过将 Load.load_only()
应用于生成的选项对象,当加载关系的对象时,生成的 SELECT 语句将仅引用 title
列以及主键列:
>>> from sqlalchemy.orm import selectinload >>> stmt = select(User).options(selectinload(User.books).load_only(Book.title)) >>> for user in session.scalars(stmt): ... print(f"{user.fullname} {[b.title for b in user.books]}") SELECT user_account.id, user_account.name, user_account.fullname FROM user_account [...] () SELECT book.owner_id AS book_owner_id, book.id AS book_id, book.title AS book_title FROM book WHERE book.owner_id IN (?, ?) [...] (1, 2) Spongebob Squarepants ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath'] Sandy Cheeks ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']
load_only()
也可以应用于子实体,而无需声明要用于关系本身的加载样式。如果我们不想更改 User.books
的默认加载样式,但仍要对 Book
应用仅加载规则,我们将使用 defaultload()
选项进行链接,在这种情况下,将保留 "lazy"
的默认关系加载样式,并将我们的自定义 load_only()
规则应用于为每个 User.books
集合发出的 SELECT 语句:
>>> from sqlalchemy.orm import defaultload >>> stmt = select(User).options(defaultload(User.books).load_only(Book.title)) >>> for user in session.scalars(stmt): ... print(f"{user.fullname} {[b.title for b in user.books]}") SELECT user_account.id, user_account.name, user_account.fullname FROM user_account [...] () SELECT book.id AS book_id, book.title AS book_title FROM book WHERE ? = book.owner_id [...] (1,) Spongebob Squarepants ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath'] SELECT book.id AS book_id, book.title AS book_title FROM book WHERE ? = book.owner_id [...] (2,) Sandy Cheeks ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels'] ```### 使用 `defer()` 省略特定列 `defer()` 加载选项是对 `load_only()` 的更精细的替代方案,允许将单个特定列标记为“不加载”。在下面的示例中,`defer()` 直接应用于 `.cover_photo` 列,保持所有其他列的行为不变: ```py >>> from sqlalchemy.orm import defer >>> stmt = select(Book).where(Book.owner_id == 2).options(defer(Book.cover_photo)) >>> books = session.scalars(stmt).all() SELECT book.id, book.owner_id, book.title, book.summary FROM book WHERE book.owner_id = ? [...] (2,) >>> for book in books: ... print(f"{book.title}: {book.summary}") A Nut Like No Other: some long summary Geodesic Domes: A Retrospective: another long summary Rocketry for Squirrels: yet another summary
与 load_only()
相同,未加载的列默认情况下将在使用惰性加载时自行加载:
>>> img_data = books[0].cover_photo SELECT book.cover_photo AS book_cover_photo FROM book WHERE book.id = ? [...] (4,)
可以在一个语句中使用多个 defer()
选项来标记多个列为延迟加载。
与 load_only()
相同,defer()
选项也包括使延迟属性在访问时引发异常而不是惰性加载的功能。这在使用 raiseload 防止延迟列加载一节中进行了说明。### 使用 raiseload 防止延迟列加载
当使用 load_only()
或 defer()
加载器选项时,对象上标记为延迟的属性具有默认行为,即在首次访问时,将在当前事务中发出 SELECT 语句以加载其值。通常需要阻止此加载操作,并在访问属性时引发异常,指示不期望为此列查询数据库的需要。典型的情况是加载具有操作所需的所有已知列的对象,然后将它们传递到视图层。视图层中发出的任何进一步的 SQL 操作都应该被捕获,以便调整前期加载操作以适应那些额外的数据,而不是额外的惰性加载。
对于这种用例,defer()
和 load_only()
选项包括一个布尔参数 defer.raiseload
,当设置为 True
时,将导致受影响的属性在访问时引发异常。在下面的示例中,延迟加载的列 .cover_photo
将禁止属性访问:
>>> book = session.scalar( ... select(Book).options(defer(Book.cover_photo, raiseload=True)).where(Book.id == 4) ... ) SELECT book.id, book.owner_id, book.title, book.summary FROM book WHERE book.id = ? [...] (4,) >>> book.cover_photo Traceback (most recent call last): ... sqlalchemy.exc.InvalidRequestError: 'Book.cover_photo' is not available due to raiseload=True
当使用 load_only()
命名一组特定的非延迟加载列时,可以使用 load_only.raiseload
参数将 raiseload
行为应用于其余列,该参数将应用于所有延迟加载属性:
>>> session.expunge_all() >>> book = session.scalar( ... select(Book).options(load_only(Book.title, raiseload=True)).where(Book.id == 5) ... ) SELECT book.id, book.title FROM book WHERE book.id = ? [...] (5,) >>> book.summary Traceback (most recent call last): ... sqlalchemy.exc.InvalidRequestError: 'Book.summary' is not available due to raiseload=True
注意
目前尚不能在一个语句中混合使用指向同一实体的 load_only()
和 defer()
选项,以改变某些属性的 raiseload
行为;目前,这样做会产生未定义的属性加载行为。
另请参见
defer.raiseload
功能是关系的同一“raiseload”功能的列级版本。有关关系的“raiseload”,请参见本指南的关系加载技术部分中的使用 load_only()
减少加载的列。
当已知只有少数几列将被访问时,load_only()
加载器选项是最方便的选项。该选项接受一个变量数量的类绑定属性对象,指示应该加载的列映射属性,除了主键之外的所有其他列映射属性都不会成为获取的列的一部分。在下面的示例中,Book
类包含列 .title
、.summary
和 .cover_photo
。使用load_only()
,我们可以指示 ORM 仅预先加载 .title
和 .summary
列:
>>> from sqlalchemy import select >>> from sqlalchemy.orm import load_only >>> stmt = select(Book).options(load_only(Book.title, Book.summary)) >>> books = session.scalars(stmt).all() SELECT book.id, book.title, book.summary FROM book [...] () >>> for book in books: ... print(f"{book.title} {book.summary}") 100 Years of Krabby Patties some long summary Sea Catch 22 another long summary The Sea Grapes of Wrath yet another summary A Nut Like No Other some long summary Geodesic Domes: A Retrospective another long summary Rocketry for Squirrels yet another summary
在上面的示例中,SELECT 语句省略了 .cover_photo
列,仅包含了 .title
和 .summary
,以及主键列 .id
;ORM 通常会获取主键列,因为这些列是必需的,以建立行的标识。
加载后,对象通常将对其余未加载的属性应用惰性加载行为,这意味着首次访问任何属性时,将在当前事务中发出 SQL 语句以加载值。下面,访问 .cover_photo
会发出一个 SELECT 语句来加载它的值:
>>> img_data = books[0].cover_photo SELECT book.cover_photo AS book_cover_photo FROM book WHERE book.id = ? [...] (1,)
惰性加载始终使用对象所处的处于持久状态的 Session
发出。如果对象从任何Session
中分离,操作将失败,引发异常。
作为访问时惰性加载的替代方案,还可以配置延迟列以在访问时引发信息性异常,而不考虑它们的附加状态。在使用load_only()
构造时,可以使用load_only.raiseload
参数来指示此情况。有关背景和示例,请参阅使用 raiseload 防止延迟列加载部分。
提示
正如在其他地方所指出的,使用异步 I/O(asyncio)时不可用惰性加载。
使用 load_only()
处理多个实体
load_only()
限制了其属性列表中所引用的单个实体(当前不允许传递跨越多个实体的属性列表)。在下面的示例中,给定的 load_only()
选项仅适用于 Book
实体。被选中的 User
实体不受影响;在生成的 SELECT 语句中,user_account
的所有列都存在,而 book
表只有 book.id
和 book.title
:
>>> stmt = select(User, Book).join_from(User, Book).options(load_only(Book.title)) >>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname, book.id AS id_1, book.title FROM user_account JOIN book ON user_account.id = book.owner_id
如果我们想要同时将 load_only()
选项应用于 User
和 Book
,我们将使用两个单独的选项:
>>> stmt = ( ... select(User, Book) ... .join_from(User, Book) ... .options(load_only(User.name), load_only(Book.title)) ... ) >>> print(stmt) SELECT user_account.id, user_account.name, book.id AS id_1, book.title FROM user_account JOIN book ON user_account.id = book.owner_id
对相关对象和集合使用 load_only()
在使用 关系加载器 控制相关对象加载时,任何关系加载器的 Load.load_only()
方法都可以用于将 load_only()
规则应用于子实体上的列。在下面的示例中,selectinload()
用于加载每个 User
对象上的相关 books
集合。通过将 Load.load_only()
应用于结果选项对象,当为关系加载对象时,生成的 SELECT 仅引用 title
列以及主键列:
>>> from sqlalchemy.orm import selectinload >>> stmt = select(User).options(selectinload(User.books).load_only(Book.title)) >>> for user in session.scalars(stmt): ... print(f"{user.fullname} {[b.title for b in user.books]}") SELECT user_account.id, user_account.name, user_account.fullname FROM user_account [...] () SELECT book.owner_id AS book_owner_id, book.id AS book_id, book.title AS book_title FROM book WHERE book.owner_id IN (?, ?) [...] (1, 2) Spongebob Squarepants ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath'] Sandy Cheeks ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']
load_only()
还可以应用于子实体,而无需声明要在关系本身使用的加载样式。如果我们不想改变 User.books
的默认加载样式,但仍要对 Book
应用加载规则,我们将使用 defaultload()
选项进行关联,在这种情况下,将保留默认关系加载样式 "lazy"
,并将我们的自定义 load_only()
规则应用于为每个 User.books
集合发出的 SELECT 语句:
>>> from sqlalchemy.orm import defaultload >>> stmt = select(User).options(defaultload(User.books).load_only(Book.title)) >>> for user in session.scalars(stmt): ... print(f"{user.fullname} {[b.title for b in user.books]}") SELECT user_account.id, user_account.name, user_account.fullname FROM user_account [...] () SELECT book.id AS book_id, book.title AS book_title FROM book WHERE ? = book.owner_id [...] (1,) Spongebob Squarepants ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath'] SELECT book.id AS book_id, book.title AS book_title FROM book WHERE ? = book.owner_id [...] (2,) Sandy Cheeks ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']
使用 load_only()
处理多个实体
load_only()
限制了其属性列表中所引用的单个实体(当前不允许传递跨越多个实体的属性列表)。在下面的示例中,给定的 load_only()
选项仅适用于 Book
实体。被选中的 User
实体不受影响;在生成的 SELECT 语句中,user_account
的所有列都存在,而 book
表只有 book.id
和 book.title
:
>>> stmt = select(User, Book).join_from(User, Book).options(load_only(Book.title)) >>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname, book.id AS id_1, book.title FROM user_account JOIN book ON user_account.id = book.owner_id
如果我们想要将load_only()
选项应用于User
和Book
,我们将使用两个单独的选项:
>>> stmt = ( ... select(User, Book) ... .join_from(User, Book) ... .options(load_only(User.name), load_only(Book.title)) ... ) >>> print(stmt) SELECT user_account.id, user_account.name, book.id AS id_1, book.title FROM user_account JOIN book ON user_account.id = book.owner_id
在相关对象和集合上使用 load_only()
当使用关系加载器来控制相关对象的加载时,可以使用任何关系加载器的Load.load_only()
方法将load_only()
规则应用于子实体上的列。在下面的示例中,使用selectinload()
加载每个User
对象上的相关books
集合。通过将Load.load_only()
应用于结果选项对象,当为关系加载对象时,生成的 SELECT 将仅引用title
列以及主键列:
>>> from sqlalchemy.orm import selectinload >>> stmt = select(User).options(selectinload(User.books).load_only(Book.title)) >>> for user in session.scalars(stmt): ... print(f"{user.fullname} {[b.title for b in user.books]}") SELECT user_account.id, user_account.name, user_account.fullname FROM user_account [...] () SELECT book.owner_id AS book_owner_id, book.id AS book_id, book.title AS book_title FROM book WHERE book.owner_id IN (?, ?) [...] (1, 2) Spongebob Squarepants ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath'] Sandy Cheeks ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']
load_only()
也可以应用于子实体,而无需说明用于关系本身的加载样式。如果我们不想更改User.books
的默认加载样式,但仍要将加载仅规则应用于Book
,我们将使用defaultload()
选项进行链接,在这种情况下,将保留默认关系加载样式"lazy"
,并将我们的自定义load_only()
规则应用于为每个User.books
集合发出的 SELECT 语句:
>>> from sqlalchemy.orm import defaultload >>> stmt = select(User).options(defaultload(User.books).load_only(Book.title)) >>> for user in session.scalars(stmt): ... print(f"{user.fullname} {[b.title for b in user.books]}") SELECT user_account.id, user_account.name, user_account.fullname FROM user_account [...] () SELECT book.id AS book_id, book.title AS book_title FROM book WHERE ? = book.owner_id [...] (1,) Spongebob Squarepants ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath'] SELECT book.id AS book_id, book.title AS book_title FROM book WHERE ? = book.owner_id [...] (2,) Sandy Cheeks ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']
使用 defer()
来省略特定列
defer()
加载器选项是对load_only()
的更精细的替代,它允许将单个特定列标记为“不加载”。在下面的示例中,直接应用defer()
到.cover_photo
列,保持所有其他列的行为不变:
>>> from sqlalchemy.orm import defer >>> stmt = select(Book).where(Book.owner_id == 2).options(defer(Book.cover_photo)) >>> books = session.scalars(stmt).all() SELECT book.id, book.owner_id, book.title, book.summary FROM book WHERE book.owner_id = ? [...] (2,) >>> for book in books: ... print(f"{book.title}: {book.summary}") A Nut Like No Other: some long summary Geodesic Domes: A Retrospective: another long summary Rocketry for Squirrels: yet another summary
与load_only()
一样,默认情况下未加载的列在使用惰性加载时会自行加载:
>>> img_data = books[0].cover_photo SELECT book.cover_photo AS book_cover_photo FROM book WHERE book.id = ? [...] (4,)
可以在一条语句中使用多个defer()
选项,以将多个列标记为延迟加载。
与load_only()
一样,defer()
选项也包括将延迟属性在访问时引发异常而不是惰性加载的能力。这在 使用 raiseload 防止延迟列加载 部分中有所说明。
使用 raiseload 防止延迟加载列
当使用 load_only()
或 defer()
加载器选项时,标记为延迟加载的对象属性在首次访问时具有默认行为,即在当前事务中发出 SELECT 语句以加载其值。通常需要阻止此加载的发生,并在访问属性时引发异常,表示不期望需要查询数据库以获取此列的需求。典型场景是使用已知需要用于操作进行的所有列加载对象,然后将其传递到视图层。应捕获视图层内发出的任何进一步的 SQL 操作,以便可以调整预先加载的操作以适应该额外的数据,而不是产生额外的惰性加载。
对于此用例,defer()
和 load_only()
选项包括一个布尔参数 defer.raiseload
,当设置为 True
时,将导致受影响的属性在访问时引发异常。在下面的示例中,延迟列 .cover_photo
将禁止属性访问:
>>> book = session.scalar( ... select(Book).options(defer(Book.cover_photo, raiseload=True)).where(Book.id == 4) ... ) SELECT book.id, book.owner_id, book.title, book.summary FROM book WHERE book.id = ? [...] (4,) >>> book.cover_photo Traceback (most recent call last): ... sqlalchemy.exc.InvalidRequestError: 'Book.cover_photo' is not available due to raiseload=True
当使用 load_only()
命名一组特定的非延迟加载列时,可以使用 load_only.raiseload
参数将 raiseload
行为应用于其余列,该参数将应用于所有延迟加载的属性:
>>> session.expunge_all() >>> book = session.scalar( ... select(Book).options(load_only(Book.title, raiseload=True)).where(Book.id == 5) ... ) SELECT book.id, book.title FROM book WHERE book.id = ? [...] (5,) >>> book.summary Traceback (most recent call last): ... sqlalchemy.exc.InvalidRequestError: 'Book.summary' is not available due to raiseload=True
注意
目前还不能混合使用 load_only()
和 defer()
选项,这两个选项指向同一个实体,在一个语句中改变某些属性的 raiseload
行为;目前这样做会产生未定义的属性加载行为。
另请参阅
defer.raiseload
特性是与关系对应的相同“raiseload”特性的列级版本。有关关系的“raiseload”,请参见防止不必要的惰性加载使用 raiseload 在本指南的关系加载技术部分。
SqlAlchemy 2.0 中文文档(十八)(4)https://developer.aliyun.com/article/1562923