Сессия и 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, остальные разбираются в тренажёре.
- Чем 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 отменит и зафлашенное.
- Добавили объект в сессию и, не делая commit, выполнили select по этой таблице. Увидим новый объект?A)Только если вручную сбросить кэш сессии перед выполнениемB)Нет: пока не вызван commit, запрос его в упор не видитC)Да: autoflush отправит pending-изменения перед запросомD)Нет, потому что select читает данные в обход сессии
показать ответ и разбор
+C)Да: autoflush отправит pending-изменения перед запросом// разбор: По умолчанию сессия с autoflush перед выполнением запроса флашит pending-изменения в транзакцию, поэтому свежедобавленный объект уже виден этому select (внутри той же транзакции). commit не нужен — но и не зафиксировано: откат всё отменит.
- Дважды запросили одну строку по её id в рамках одной сессии. Сколько объектов Python получим?A)Один и тот же объект — работает identity mapB)Два объекта, но второй будет доступен только на чтениеC)Зависит от того, был ли между запросами вызван commit сессииD)Два разных объекта с одинаковыми значениями внутри полей
показать ответ и разбор
+A)Один и тот же объект — работает identity map// разбор: Сессия держит identity map: для каждого первичного ключа — ровно один объект. Повторный запрос той же строки вернёт тот же экземпляр из карты, а не создаст дубль. Это гарантирует согласованность изменений и экономит память в пределах сессии.
- После
commit()с настройкой по умолчанию обращаемся к атрибуту объекта. Что под капотом?A)Значения берутся из памяти без единого обращения к базе данныхB)Атрибуты истекли — идёт перезагрузка из базыC)Объект молча возвращает устаревшие значения до момента откатаD)Обращение падает с ошибкой до открытия новой сессиипоказать ответ и разбор
+B)Атрибуты истекли — идёт перезагрузка из базы// разбор: По умолчанию expire_on_commit=True: после commit атрибуты объектов помечаются истёкшими, и первое обращение к ним внутри живой сессии триггерит SELECT-перезагрузку — данные гарантированно свежие. Вне сессии это же обращение даст DetachedInstanceError. Отключают через expire_on_commit=False.
- Что моделирует сессия SQLAlchemy как unit of work?A)Открывает отдельную транзакцию и коммитит её на каждое изменение объектаB)Кеширует результаты всех SELECT-запросов между разными запросами приложенияC)Копит изменения объектов и применяет их в БД одной транзакцией на commitD)Хранит открытое соединение навсегда, разделяя его между всеми пользователями сразу
показать ответ и разбор
+C)Копит изменения объектов и применяет их в БД одной транзакцией на commit// разбор: Сессия — единица работы: вы добавляете, меняете, удаляете объекты, а она отслеживает изменения (dirty/new/deleted) и на commit применяет их к БД одной транзакцией. Это даёт согласованность (всё или ничего) и оптимизацию (сгруппированные операции). Обычно сессию открывают на запрос/операцию и закрывают по завершении, а не держат одну на всё приложение.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.