Код-ревью Senior SQLAlchemy 2.0 обновлено

Код-ревью async-сервиса на SQLAlchemy

Вопрос

Найдите проблемы в функции, которая собирает сводку по пользователям.

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from .models import User


async def users_summary(session: AsyncSession):
    result = await session.execute(select(User))
    users = result.scalars().all()
    out = []
    for u in users:
        orders = u.orders
        total = sum(o.amount for o in orders)
        out.append({'email': u.email, 'total': total})
        u.last_report_total = total
        await session.commit()
    return out

Коротко

Агрегацию лучше перенести в базу: Если сохранять итог в lastreporttotal действительно нужно, это делается одним update с подзапросом или пачкой изменений и одним коммитом.

Что должен найти ревьюер

  1. Производительностьстрока 7
    Загружаются все пользователи целиком без фильтра и пагинации, со всеми колонками
  2. Ошибкастрока 11
    Ленивая загрузка u.orders в async падает с MissingGreenlet; нужен selectinload(User.orders)
  3. Производительностьстрока 12
    Сумма считается в Python перебором заказов; агрегировать в базе через func.sum и group_by
  4. Производительностьстрока 15
    Коммит на каждого пользователя: много транзакций, а после коммита атрибуты истекают (expire_on_commit) и следующее обращение снова пойдёт в базу; один коммит в конце или массовый update

Разбор

Агрегацию лучше перенести в базу:

async def users_summary(session: AsyncSession, limit: int = 100, offset: int = 0):
    stmt = (
        select(User.id, User.email, func.coalesce(func.sum(Order.amount), 0).label('total'))
        .outerjoin(Order, Order.user_id == User.id)
        .group_by(User.id, User.email)
        .order_by(User.id)
        .limit(limit).offset(offset)
    )
    rows = (await session.execute(stmt)).all()
    return [{'email': r.email, 'total': r.total} for r in rows]

Если сохранять итог в last_report_total действительно нужно, это делается одним update с подзапросом или пачкой изменений и одним коммитом.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.

Термины по теме