Значение lazy joined
Значение lazy='joined' передается в параметр
lazy отношения relationship и включает
жадную загрузку связанных объектов. При выборке родителя
SQLAlchemy сразу добавляет к основному запросу
LEFT OUTER JOIN и загружает дочерние записи
в том же запросе. Это избавляет от эффекта N+1,
когда для каждой родительской строки выполняется
отдельный дополнительный запрос.
Отношение объявляется через функцию relationship,
в которой первым параметром передается имя связанного
класса, а параметр lazy принимает строку
'joined'. По умолчанию для отношений
используется значение 'select', то есть ленивая
загрузка отдельными запросами.
Синтаксис
relationship(ClassName, lazy='joined')
Пример
Давайте создадим две связанные модели и настроим жадную
загрузку через lazy='joined':
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = 'users'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
articles: Mapped[list['Article']] = relationship(lazy='joined')
class Article(Base):
__tablename__ = 'articles'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
user_id: Mapped[int] = mapped_column(ForeignKey('users.id'))
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
with Session(engine) as session:
user = User(name='user', articles=[Article(title='article')])
session.add(user)
session.commit()
res = session.query(User).all()
print(res[0].articles)
Результат выполнения кода:
[Article(id=1, title='article', user_id=1)]
Пример
Давайте включим вывод SQL-запросов и посмотрим,
что связанные объекты загружаются одним запросом
с LEFT OUTER JOIN:
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = 'users'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
articles: Mapped[list['Article']] = relationship(lazy='joined')
class Article(Base):
__tablename__ = 'articles'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
user_id: Mapped[int] = mapped_column(ForeignKey('users.id'))
engine = create_engine('sqlite:///:memory:', echo=True)
Base.metadata.create_all(engine)
with Session(engine) as session:
user = User(name='user', articles=[Article(title='article')])
session.add(user)
session.commit()
res = session.query(User).all()
print(res[0].articles[0].title)
Итоговый SQL запрос:
SELECT users.id AS users_id, users.name AS users_name,
articles_1.id AS articles_1_id,
articles_1.title AS articles_1_title,
articles_1.user_id AS articles_1_user_id
FROM users
LEFT OUTER JOIN articles AS articles_1 ON users.id = articles_1.user_id
Пример
Давайте убедимся, что при обращении к связи после основного запроса дополнительный SQL не выполняется:
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = 'users'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
articles: Mapped[list['Article']] = relationship(lazy='joined')
class Article(Base):
__tablename__ = 'articles'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
user_id: Mapped[int] = mapped_column(ForeignKey('users.id'))
engine = create_engine('sqlite:///:memory:', echo=True)
Base.metadata.create_all(engine)
with Session(engine) as session:
user = User(name='user', articles=[Article(title='article')])
session.add(user)
session.commit()
session.expire_all()
res = session.query(User).all()
print('loaded:', res[0].articles[0].title)
В логе видно только один SELECT с джойном,
а строка loaded: article выводится без
дополнительного запроса:
"loaded: article"
Смотрите также
-
функцию
relationship,
которая настраивает связи между моделями -
значение
lazy.select,
которое включает ленивую загрузку отдельным запросом -
функцию
joinedload,
которая задает жадную загрузку на уровне запроса -
функцию
selectinload,
которая загружает связи через отдельный IN-запрос