AsyncSession и asyncio.gather: почему нельзя делить одну сессию

Для страницы кабинета нужно получить профиль, заказы и уведомления. Эти запросы независимы, поэтому хочется обернуть их в asyncio.gather. Однако ускорение ломается, если всем функциям передать одну AsyncSession: у сессии есть состояние транзакции, и несколько задач начинают управлять им одновременно.

Полезно сначала решить, что здесь важнее: независимое выполнение или общая транзакция. Для независимых чтений можно выдать каждой задаче свою сессию. Для связанных изменений обычно нужна одна транзакция с последовательными операциями.

Минимальный пример без сервера базы

Установите python -m pip install sqlalchemy aiosqlite. Этот файл использует SQLite в памяти только для демонстрации владения сессиями.

import asyncio
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker

async def main():
    engine = create_async_engine("sqlite+aiosqlite:///:memory:")
    sessions = async_sessionmaker(engine, expire_on_commit=False)

    async def read_value(value: int):
        async with sessions() as session:
            return await session.scalar(text("SELECT :value"), {"value": value})

    try:
        result = await asyncio.gather(read_value(10), read_value(20))
        print(result)
    finally:
        await engine.dispose()

asyncio.run(main())

Результат — [10, 20]. Объект sessions — фабрика, которую можно передавать функциям. Конкретная session создаётся внутри каждой задачи и закрывается там же. Для PostgreSQL поменяется драйвер и URL подключения; правило владения останется тем же.

Почему две сессии не заменяют одну транзакцию

Допустим, нужно создать заказ и списать резерв. Если разнести изменения по разным сессиям и каждая сделает commit, одна операция может сохраниться, а другая завершиться ошибкой. Параллельность появилась, целостность бизнес-действия исчезла.

Для такого случая обе записи делают последовательно внутри одного session.begin(). Если второй шаг падает, транзакция откатывается. Переделывать это в gather ради меньшего времени ответа без изменения всей схемы согласованности не стоит.

Что проверить под нагрузкой

Каждая сессия при обращении к базе потребует ресурсы пула. Если один HTTP-запрос запускает двадцать параллельных чтений, а пользователей одновременно сто, ограниченный пул быстро станет очередью ожидания. Сначала измерьте количество SQL-запросов: иногда один JOIN или пакетное чтение полезнее десятков конкурентных вызовов.

Отдельно проверьте отмену: обработчики должны выходить из контекстных менеджеров и возвращать соединения. Тест с двумя SELECT показывает структуру кода, но не доказывает выигрыш скорости на рабочей базе. Для выбора размеров пула есть расчёт соединений по процессам и репликам.

Собрать следующий этап проекта

Хотите собрать API целиком, а не останавливаться на отдельном обработчике? На бесплатном курсе FastAPI вы сделаете сервис с базой, регистрацией, проверкой прав и тестами. Это проект, который можно развивать дальше и показывать вместе с исходниками.

Посмотреть программу: FastAPI для начинающих: API с базой данных и тестами

Проверить детали: SQLAlchemy: AsyncSession и конкурентные задачи.

Продолжить на практике

  • FastAPI для начинающих: API с базой данных и тестами ↗

    Соберёте свой первый полноценный backend на FastAPI: с базой данных, регистрацией, JWT-авторизацией и автотестами. 34 коротких урока и 140 заданий с проверкой — бесплатно.

  • Python для продвинутых: ООП, типизация и тестирование ↗

    Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.