В чём разница между expire_on_commit=True и expire_on_commit=False?
expire_on_commit=True (по умолчанию) помечает объекты expired после commit — следующее чтение атрибута делает SELECT. False оставляет данные в памяти, но нужен явный refresh() для серверных значений.
expire_on_commit — поведение объектов после коммита
Параметр expire_on_commit управляет тем, что происходит с ORM-объектами в сессии сразу после успешного session.commit().
expire_on_commit=True (по умолчанию)
После каждого commit() SQLAlchemy помечает все persistent-объекты как expired. При следующем обращении к любому атрибуту объекта автоматически выполняется SELECT для обновления значений из БД.
from sqlalchemy.orm import Session
# expire_on_commit=True — поведение по умолчанию
with Session(engine) as session:
user = User(name="Alice")
session.add(user)
session.commit()
# Здесь user — expired. Следующая строка вызовет SELECT:
print(user.name) # SELECT users WHERE id=1 → 'Alice'
Это гарантирует, что вы всегда читаете актуальные данные из БД (включая server_default, триггеры, generated columns).
expire_on_commit=False
Объекты не помечаются как expired после commit. Атрибуты остаются в памяти Python — SELECT не происходит при следующем обращении.
from sqlalchemy.orm import sessionmaker
SessionLocal = sessionmaker(engine, expire_on_commit=False)
with SessionLocal() as session:
user = User(name="Bob")
session.add(user)
session.commit()
# Нет lazy SELECT — читаем из памяти:
print(user.name) # 'Bob', без запроса к БД
print(user.id) # id заполнен после flush (не пустой)
Когда использовать каждый вариант
- True — когда важна консистентность: trigrры в БД,
server_default(created_at, uuid), конкурентные обновления. - False — async-код с FastAPI/asyncpg, где лишние SELECT после commit создают проблемы; возврат объекта из эндпоинта сразу после создания без дополнительного
refresh().
# Типичный FastAPI паттерн с async:
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
AsyncSessionLocal = async_sessionmaker(
async_engine,
expire_on_commit=False # избегаем lazy SELECT вне async context
)
async def create_user(data: UserCreate) -> User:
async with AsyncSessionLocal() as session:
user = User(**data.model_dump())
session.add(user)
await session.commit()
await session.refresh(user) # явно получаем id и server defaults
return user
Разница в поведении при detach
# expire_on_commit=True:
with Session(engine) as session:
user = session.get(User, 1)
session.commit() # user теперь expired
# После выхода из контекста — detached:
# user.name → DetachedInstanceError (нельзя lazy load)
# expire_on_commit=False:
with SessionLocal() as session: # expire_on_commit=False
user = session.get(User, 1)
session.commit() # user НЕ expired, данные в памяти
# После выхода:
print(user.name) # OK — читаем из памяти без SELECT
Подводные камни
- С
expire_on_commit=Trueобращение к атрибуту вне сессии (detached state) бросаетDetachedInstanceError— частая ошибка при сериализации Pydantic-схем после commit. - С
expire_on_commit=Falseвы можете прочитать устаревшие данные: если между двумя коммитами другая транзакция обновила строку, ваш объект этого не знает. - В async-коде
expire_on_commit=True+ lazy attribute access внеasync with sessionдаётMissingGreenletвместо понятной ошибки. session.refresh(obj)всегда делает SELECT независимо отexpire_on_commit— используйте его явно, когда нужны актуальные значения server defaults.- Настройка применяется на уровне
sessionmaker, не на уровне отдельного запроса — изменение поведения для одного коммита требует явногоsession.expire(obj)илиsession.refresh(obj). - При использовании scoped_session в многопоточном коде
expire_on_commit=Falseувеличивает риск чтения stale data между запросами. - Комбинация
expire_on_commit=True+ возврат объекта из функции после закрытия сессии — самая частая причинаDetachedInstanceErrorв production.
Common mistakes
- Описывать expire on commit только как термин и не показывать механизм на минимальном примере.
- Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
- Не связывать поведение с официальным контрактом SQLAlchemy и реальной эксплуатацией.
What the interviewer is testing
- Объясняет expire on commit через последовательность действий, а не через набор ключевых слов.
- Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
- Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.