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 к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.
