Sqlalchemy loader strategy bd

Ссылка на статью

[sqlalchemy] ОРИ загружает объекты лениво (laizy load), по мере их востребованности. Этот шаблон проектирования довольно противоречив - когда в памяти несколько десятков объектов, которые ссылаются на несколько других объектов, ленивая загрузка может привести к избыточным запросам к БД. Такие запросы выполняются неявно, могут провоцировать ошибки и, что важно, не работают с [asyncio]. При этом ленивая (отложенная) загрузка имеет много преиуществ, если совместима с параллельной обработкой.

Задача применении ОРМ сводится к запуску SQL echoing с целью проверки избыточности одних и тех-же селектов и дальнешем применении различных стратегий загрузки.

Стратегия загрузки может быть представлена в виде объекта, ассоциированного с методом Select.options()

for user_obj in session.execute(
    select(User).options(selectinload(User.addresses))
).scalars():
    user_obj.addresses  # access addresses collection already loaded

Кроме того, стратегия может быть реализована в relationship() через опцию relationship.lazy

from sqlalchemy.orm import relationship
class User(Base):
    __tablename__ = 'user_account'

    addresses = relationship("Address", back_populates="user", lazy="selectin")

Selectin load

selectinload() наиболее используемая опция лоадера в [sqlalchemy]. Опция гарантирует, что конкретная коллекция для полной серии объектов загружается заранее с помощью одного запроса. Он делает это с помощью SELECT, который в большинстве случаев может быть отправлен только для связанной таблицы, без введения JOIN или подзапросов, и только для тех родительских объектов, для которых коллекция еще не загружена. Пример:

>>> from sqlalchemy.orm import selectinload
>>> stmt = (
...   select(User).options(selectinload(User.addresses)).order_by(User.id)
... )
>>> for row in session.execute(stmt):
...     print(f"{row.User.name}  ({', '.join(a.email_address for a in row.User.addresses)})")
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account ORDER BY user_account.id
[...] ()
SELECT address.user_id AS address_user_id, address.id AS address_id,
address.email_address AS address_email_address
FROM address
WHERE address.user_id IN (?, ?, ?, ?, ?, ?)
[...] (1, 2, 3, 4, 5, 6)
spongebob  (spongebob@sqlalchemy.org)
sandy  (sandy@sqlalchemy.org, sandy@squirrelpower.org)
patrick  ()
squidward  ()
ehkrabs  ()
pkrabs  (pearl.krabs@gmail.com, pearl@aol.com)

Joined load

joinload() дополняет инструкцию SELECT, передаваемую в базу данных, с помощью JOIN (который может быть внешним или внутренним соединением в зависимости от параметров), которае затем может загружаться в связанные объекты. лучше всего подходит для загрузки связанных объектов типа “многие к одному”, поскольку для этого требуется только добавить дополнительные столбцы в строку первичной сущности, которая будет извлечена в любом случае. Для большей эффективности может принимать еще и joinedload.innerjoin. Пример

>>> from sqlalchemy.orm import joinedload
>>> stmt = (
...   select(Address).options(joinedload(Address.user, innerjoin=True)).order_by(Address.id)
... )
>>> for row in session.execute(stmt):
...     print(f"{row.Address.email_address} {row.Address.user.name}")
SELECT address.id, address.email_address, address.user_id, user_account_1.id AS id_1,
user_account_1.name, user_account_1.fullname
FROM address
JOIN user_account AS user_account_1 ON user_account_1.id = address.user_id
ORDER BY address.id
[...] ()
spongebob@sqlalchemy.org spongebob
sandy@sqlalchemy.org sandy
sandy@squirrelpower.org sandy
pearl.krabs@gmail.com pkrabs
pearl@aol.com pkrabs

Может так-же использоваться для коллекций один ко многим, но имеет эффект рекурсивного умножения первичных строк на связный элемент, что может быть затратно.

Explicit Join + Eager load

Augmenting Loader Strategy Paths

Raiseload

Позволяет в приципе блокировать “отложенную загрузку”. Пример:

class User(Base):
    __tablename__ = 'user_account'

    # ... Column mappings

    addresses = relationship("Address", back_populates="user", lazy="raise_on_sql")


class Address(Base):
    __tablename__ = 'address'

    # ... Column mappings

    user = relationship("User", back_populates="addresses", lazy="raise_on_sql")