Как работать с асинхронностью в SQLAlchemy
Что проверяют
Понимаете ли вы, как ORM с ленивой загрузкой уживается с asyncio, где любое неявное обращение к базе запрещено. Следом почти всегда спрашивают про MissingGreenlet и про то, можно ли делить одну сессию между задачами.
Как отвечать
Речь о SQLAlchemy 2.0 и новее, расширение sqlalchemy.ext.asyncio. Каркас:
- драйвер: асинхронный, например
postgresql+asyncpg://,postgresql+psycopg://(psycopg 3 умеет async) илиsqlite+aiosqlite://. Ставьтеsqlalchemy[asyncio], чтобы подтянулсяgreenlet, на нём держится вся интеграция; - движок и фабрика:
create_async_engine(url, pool_pre_ping=True)иasync_sessionmaker(engine, expire_on_commit=False). Старый приёмsessionmaker(class_=AsyncSession)работает, но в 2.0 для этого есть отдельная фабрика; - запросы:
await session.execute(select(User).where(...)), затем.scalars().all(), или корочеawait session.scalars(...). Сырой SQL только черезtext(): обычную строкуexecuteв 2.0 не принимает; - связи: ленивая загрузка при обращении к атрибуту в async-коде даёт
MissingGreenlet. Связи грузят явно черезselectinloadилиjoinedloadлибо ставятlazy="raise", чтобы ошибка ловилась сразу. Есть и миксинAsyncAttrsсawait obj.awaitable_attrs.items; - транзакции:
async with session.begin():коммитит или откатывает сам; - синхронные операции:
await conn.run_sync(Base.metadata.create_all); - жизненный цикл: в FastAPI сессия на запрос через зависимость с
yield, при остановкеawait engine.dispose().
expire_on_commit=False важен: иначе после коммита атрибуты объектов помечаются устаревшими, и следующее чтение снова попытается сходить в базу неявно.
Где спотыкаются
Делят одну AsyncSession между корутинами в asyncio.gather. Сессия не рассчитана на конкурентное использование, нужна отдельная на каждую задачу.
Путают асинхронность с многопоточностью и ждут ускорения тяжёлых запросов. Async помогает держать много одновременных ожиданий, а сам запрос быстрее не становится.
Вызывают синхронный код (requests, синхронный драйвер, тяжёлый расчёт) внутри обработчика и блокируют весь event loop.