Функция immediateload
Функция immediateload включает стратегию
немедленной загрузки связанных объектов. При
использовании этой стратегии SQLAlchemy для
каждой строки основного запроса выполняет
отдельный дополнительный SELECT, загружающий
связанные данные. Первым параметром передаётся
атрибут связи, для которого нужно применить
стратегию загрузки.
Стратегия immediateload похожа на
lazyload, но загрузка происходит сразу
после выполнения основного запроса, а не в
момент первого обращения к атрибуту. Это
позволяет избежать неожиданных дополнительных
запросов при работе с объектами, но приводит
к эффекту N+1 - по одному SELECT на каждую
связанную строку.
Синтаксис
from sqlalchemy.orm import immediateload
immediateload(attribute)
Пример
Давайте создадим модели статьи и пользователя,
связанные отношением один-ко-многим, и
применим стратегию immediateload к
связи:
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = 'users'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(50))
articles: Mapped[list['Article']] = relationship(
back_populates='author',
lazy='immediateload',
)
class Article(Base):
__tablename__ = 'articles'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(100))
text: Mapped[str] = mapped_column(String(500))
status: Mapped[str] = mapped_column(String(20))
num: Mapped[int] = mapped_column()
author_id: Mapped[int] = mapped_column(ForeignKey('users.id'))
author: Mapped['User'] = relationship(back_populates='articles')
При обращении к атрибуту articles
загрузка произойдёт сразу после основного
запроса к пользователям, без явного вызова
дополнительного запроса:
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
from models import Base, User, Article
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
with Session(engine) as session:
user = User(name='user')
user.articles = [
Article(title='article', text='text 1', status='draft', num=1),
Article(title='article', text='text 2', status='published', num=2),
]
session.add(user)
session.commit()
with Session(engine) as session:
res = session.scalars(select(User)).all()
for user in res:
print(user.name, len(user.articles))
Результат выполнения кода:
"user 2"
Пример
Давайте применим стратегию immediateload
непосредственно в запросе с помощью метода
options:
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session, immediateload
from models import Base, User, Article
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
with Session(engine) as session:
user = User(name='user')
user.articles = [
Article(title='article', text='text 1', status='draft', num=1),
]
session.add(user)
session.commit()
with Session(engine) as session:
stmt = select(User).options(immediateload(User.articles))
res = session.scalars(stmt).all()
for user in res:
print(user.name, len(user.articles))
Результат выполнения кода:
"user 1"
Пример
Давайте посмотрим, какие SQL-запросы выполняются
при стратегии immediateload, включив
параметр echo:
Итоговые SQL-запросы:
SELECT users.id, users.name FROM users
SELECT articles.id, articles.title, articles.text, articles.status, articles.num, articles.author_id FROM articles WHERE ? = articles.author_id
Смотрите также
-
функцию
lazyload,
которая откладывает загрузку связи до первого обращения -
функцию
selectinload,
которая загружает связи одним запросом с IN -
функцию
joinedload,
которая загружает связи через JOIN -
функцию
subqueryload,
которая загружает связи отдельным подзапросом