сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Базы данных в Python

Сессия и Unit of Work

Session как Unit of Work

Session в SQLAlchemy - не просто «соединение», а реализация паттерна Unit of Work, и путаница вокруг flush/commit и жизни объектов после commit даёт тонкие баги. Собес проверяет, понимаешь ли ты, когда SQL реально уходит в БД и почему объект «протухает» после коммита.

Типовые формулировки: «чем flush отличается от commit?», «увидит ли select только что добавленный объект?», «что такое identity map».

Unit of Work, flush и commit

Session копит новые, изменённые и удалённые объекты и синхронизирует их ПАЧКОЙ на commit, сам расставляя порядок INSERT/UPDATE/DELETE по зависимостям это и есть Unit of Work. Ты работаешь с объектами, а не с отдельными запросами.

flush отправляет накопленный SQL в ОТКРЫТУЮ транзакцию: данные видны внутри неё, можно взять сгенерированный id, но транзакция ещё не зафиксирована. commit фиксирует её. Ключевое: откат до commit отменит и то, что было зафлашено. Путать flush и commit - значит рассчитывать, что flush сохранил данные насовсем.

// autoflush: перед выполнением запроса сессия сама флашит pending-изменения, поэтому свежедобавленный объект уже виден этому select - внутри транзакции, без commit.

session.add(user)
session.flush()   # INSERT ушёл в транзакцию, user.id есть
# ...но rollback здесь ещё всё отменит
session.commit()  # вот теперь зафиксировано
Unit of Work
сессия копит изменения и пишет их согласованной пачкой
flush vs commit
отправить SQL в транзакцию против её фиксации

Identity map и expire после commit

Identity map: на один первичный ключ в пределах сессии - ровно ОДИН объект. Повторный запрос той же строки вернёт тот же экземпляр Python, а не дубль, поэтому изменения одного объекта не разъезжаются с «другой копией».

expire_on_commit=True (дефолт): после commit атрибуты объектов истекают, и первое обращение к ним внутри ЖИВОЙ сессии триггерит SELECT-перезагрузку свежих данных. Но если сессия уже закрыта - то же обращение даёт DetachedInstanceError. Отсюда частый expire_on_commit=False, когда объект нужно читать после commit вне сессии.

// autoflush и expire_on_commit - две настройки, объясняющие «магию» сессии: почему select видит некоммиченный объект и почему он перечитывается после commit.

autoflush
авто-flush pending-изменений перед запросом
identity map
один объект на первичный ключ в пределах сессии

Как отвечать: «Чем flush отличается от commit?»

flush выталкивает накопленный сессией SQL в текущую транзакцию: выполняются INSERT и UPDATE, становится доступен сгенерированный id, изменения видны внутри этой транзакции, но она ещё НЕ зафиксирована. commit фиксирует транзакцию окончательно. Практическая разница в том, что откат после flush, но до commit, всё отменит - flush не про сохранность, а про то, чтобы БД увидела изменения в рамках транзакции, например ради получения id или проверки ограничений. Ещё есть autoflush: сессия сама флашит перед запросом, поэтому select видит только что добавленный объект даже без явного flush.

Точно разведены «SQL в транзакцию» и «фиксация», назван практический смысл flush (id, видимость внутри транзакции) и последствие отката - видно рабочую модель транзакций.

На чём валят

  • Путать flush и commit: flush не фиксирует - откат отменит зафлашенное.
  • Ждать, что select не увидит некоммиченный объект - autoflush покажет его в транзакции.
  • Обращаться к атрибутам после commit вне сессии: expire_on_commit даёт DetachedInstanceError.
  • Ждать дубли объектов на повторный запрос строки - identity map вернёт тот же экземпляр.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.

  1. #session_uow1 / 5
    Чем flush отличается от commit в сессии SQLAlchemy?
    A)flush фиксирует в базе, а commit лишь помечает объекты как чистые
    B)flush шлёт SQL в транзакцию, commit её фиксирует
    C)flush и commit — полные синонимы, разницы в поведении нет
    D)commit шлёт SQL, а flush просто очищает кэш объектов сессии
    показать ответ и разбор
    +B)flush шлёт SQL в транзакцию, commit её фиксирует

    // разбор: flush отправляет накопленный SQL (INSERT/UPDATE/DELETE) в открытую транзакцию — изменения видны внутри неё и можно получить сгенерированные id, но они ещё не зафиксированы. commit фиксирует транзакцию (внутри тоже флашит). Откат до commit отменит и зафлашенное.

  2. #session_uow2 / 5
    Добавили объект в сессию и, не делая commit, выполнили select по этой таблице. Увидим новый объект?
    A)Только если вручную сбросить кэш сессии перед выполнением
    B)Нет: пока не вызван commit, запрос его в упор не видит
    C)Да: autoflush отправит pending-изменения перед запросом
    D)Нет, потому что select читает данные в обход сессии
    показать ответ и разбор
    +C)Да: autoflush отправит pending-изменения перед запросом

    // разбор: По умолчанию сессия с autoflush перед выполнением запроса флашит pending-изменения в транзакцию, поэтому свежедобавленный объект уже виден этому select (внутри той же транзакции). commit не нужен — но и не зафиксировано: откат всё отменит.

  3. #session_uow3 / 5
    Дважды запросили одну строку по её id в рамках одной сессии. Сколько объектов Python получим?
    A)Один и тот же объект — работает identity map
    B)Два объекта, но второй будет доступен только на чтение
    C)Зависит от того, был ли между запросами вызван commit сессии
    D)Два разных объекта с одинаковыми значениями внутри полей
    показать ответ и разбор
    +A)Один и тот же объект — работает identity map

    // разбор: Сессия держит identity map: для каждого первичного ключа — ровно один объект. Повторный запрос той же строки вернёт тот же экземпляр из карты, а не создаст дубль. Это гарантирует согласованность изменений и экономит память в пределах сессии.

  4. #session_uow4 / 5
    После commit() с настройкой по умолчанию обращаемся к атрибуту объекта. Что под капотом?
    A)Значения берутся из памяти без единого обращения к базе данных
    B)Атрибуты истекли — идёт перезагрузка из базы
    C)Объект молча возвращает устаревшие значения до момента отката
    D)Обращение падает с ошибкой до открытия новой сессии
    показать ответ и разбор
    +B)Атрибуты истекли — идёт перезагрузка из базы

    // разбор: По умолчанию expire_on_commit=True: после commit атрибуты объектов помечаются истёкшими, и первое обращение к ним внутри живой сессии триггерит SELECT-перезагрузку — данные гарантированно свежие. Вне сессии это же обращение даст DetachedInstanceError. Отключают через expire_on_commit=False.

  5. #session_uow5 / 5
    Что моделирует сессия SQLAlchemy как unit of work?
    A)Открывает отдельную транзакцию и коммитит её на каждое изменение объекта
    B)Кеширует результаты всех SELECT-запросов между разными запросами приложения
    C)Копит изменения объектов и применяет их в БД одной транзакцией на commit
    D)Хранит открытое соединение навсегда, разделяя его между всеми пользователями сразу
    показать ответ и разбор
    +C)Копит изменения объектов и применяет их в БД одной транзакцией на commit

    // разбор: Сессия — единица работы: вы добавляете, меняете, удаляете объекты, а она отслеживает изменения (dirty/new/deleted) и на commit применяет их к БД одной транзакцией. Это даёт согласованность (всё или ничего) и оптимизацию (сгруппированные операции). Обычно сессию открывают на запрос/операцию и закрывают по завершении, а не держат одну на всё приложение.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.