Что делает метод flush у сессии SQLAlchemy
Что проверяют
Видите ли вы границу между ORM и транзакцией базы. Вопрос любят задавать после общих слов про сессии: по ответу понятно, знаете ли вы, что сессия копит изменения в памяти, и что запись в базу и фиксация транзакции это два разных события.
Как отвечать
flush отправляет накопленные в сессии изменения в базу как INSERT, UPDATE и DELETE, но транзакцию не завершает. До commit эти строки видит только текущее соединение, а rollback их отменит. Сразу после вызова у новых объектов заполнены серверные значения, в первую очередь первичный ключ, и отрабатывают ограничения и триггеры базы, поэтому ошибка целостности прилетает именно здесь.
Явно звать flush нужно редко: сессия по умолчанию делает это сама перед каждым запросом, а commit начинается с того же самого. Осмысленные случаи:
- нужен идентификатор родительской записи, чтобы проставить его детям в той же транзакции;
- надо поймать нарушение уникальности внутри своей логики и обработать его;
- нужна точка сохранения через
begin_nested().
session.add(order)
session.flush() # идентификатор уже есть, транзакция открыта
item.order_id = order.id
session.add(item)
session.commit()Где спотыкаются
Считают flush сохранением. Сессия закрывается без commit, транзакция откатывается, и строк в базе нет, хотя в логах видны выполненные запросы. Обратная сторона той же путаницы: думают, что данные уже видны другим соединениям.
Продолжают работать с сессией после исключения на flush. Транзакция помечена как испорченная, и любой следующий запрос упадёт с требованием сделать откат. Правильная реакция это rollback и только потом новая попытка.
Забывают, что после commit атрибуты объектов помечаются устаревшими и первое же обращение к полю идёт в базу. Если объект к этому моменту уже вне сессии, вместо значения будет ошибка об отсоединённом экземпляре. Лечится либо чтением нужных полей до закрытия, либо отключением этого поведения осознанно.