Understanding Session.expunge vs Session.clear in Python
Session.expunge() removes a single tracked instance from the identity map while preserving its database row state, whereas Session.clear() empties the entire identity map and transitions all tracked objects to a detached state. Neither method triggers flush() or commit(). Use expunge() for selective object promotion to external caches; use clear() for stateless request teardown and memory reclamation.
Direct Answer: Core Differences & State Impact
The fundamental distinction lies in identity map scope and state transition granularity. Session.expunge(instance) detaches exactly one object, leaving the session's internal tracking dictionary intact for all other entities. The expunged object retains its primary key and database-synchronized attributes but loses ORM change-tracking capabilities. Conversely, Session.clear() performs a hard reset on the session's identity map, marking every currently tracked instance as detached.
Both operations operate strictly within the session's in-memory state management layer and do not interact with the database transaction boundary. They will not issue SQL statements, nor will they persist pending modifications. For a complete breakdown of how these methods interact with pending, persistent, and detached states, consult the ORM state transition matrices in Session Lifecycle and Scope Management. Aligning these calls with explicit transaction boundaries prevents silent data loss and ensures predictable connection pool behavior.
Exact Syntax & Method Signatures
Both methods are synchronous operations that manipulate the identity map directly. They bypass SQLAlchemy 2.0's before_attach and after_detach event listeners to guarantee zero-latency execution.
from sqlalchemy.orm import Session
def selective_removal(session: Session, target_obj: object) -> None:
"""Removes a single instance from session tracking."""
session.expunge(target_obj)
assert target_obj not in session
assert target_obj._sa_instance_state.session is None
def full_identity_reset(session: Session) -> None:
"""Clears all tracked instances from the identity map."""
session.clear()
assert len(session.identity_map) == 0
# All previously tracked objects are now in 'detached' state
Signature Comparison:
Session.expunge(instance: object) -> None: Requires a mapped instance. RaisesInvalidRequestErrorif the instance is not currently tracked.Session.clear() -> None: Accepts zero arguments. Executes unconditionally.
In AsyncSession implementations, both methods delegate synchronously to the underlying Session object. They do not require await and execute immediately on the event loop thread without blocking I/O operations.
Niche Optimization: Async Workflows & Memory Footprint
In high-concurrency async endpoints, unbounded identity map growth causes memory leaks and degrades connection pool efficiency. Strategic use of expunge() and clear() isolates ORM state from application-level lifecycles.
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from typing import Any
async def async_handler(session: AsyncSession, model_cls: Any) -> None:
async with session.begin():
result = await session.execute(select(model_cls).limit(1))
obj = result.scalar_one()
# Option 1: Promote single object to external cache (Redis/Memcached)
session.expunge(obj)
# obj is now safe to serialize/store without ORM overhead
# Option 2: Stateless worker teardown (prevents memory accumulation)
# session.clear()
Optimization Guidelines:
- Use
clear()at the end of stateless request handlers or background worker teardown phases to guarantee zero ORM cache retention between invocations. - Use
expunge()when specific hydrated objects must be promoted to application-level caches or passed to non-ORM serialization pipelines. - Align these patterns with connection lifecycle optimization strategies detailed in Mastering SQLAlchemy 2.0 Core and ORM Architecture to prevent stale connection references in long-running async loops.
Error Resolution: DetachedInstanceError & InvalidRequestError
Improper state management triggers two primary exceptions in production environments:
DetachedInstanceError: Occurs when accessing unloaded attributes or lazy-loaded relationships afterexpunge(). The instance no longer has a session to issue deferredSELECTstatements. Mitigation: Explicitly pre-load required relationships usingselectinload()orjoinedload()before detaching. Alternatively, re-attach the object viasession.add(obj)if further ORM operations are required.InvalidRequestError: Triggered when invokingclear()mid-transaction without a priorrollback(). SQLAlchemy prevents clearing the identity map while pending changes exist in an active transaction to avoid silent data loss. Mitigation: Always callsession.rollback()beforeclear()if uncommitted changes exist, or structure your code to clear only after explicit transaction boundaries.
from sqlalchemy.orm import selectinload
# Pre-loading prevents DetachedInstanceError
async def safe_expunge(session: AsyncSession, obj_id: int) -> dict:
stmt = select(User).options(selectinload(User.profile)).where(User.id == obj_id)
result = await session.execute(stmt)
user = result.scalar_one()
session.expunge(user)
return {"id": user.id, "profile_data": user.profile.data}
Production Pitfalls
- Calling
clear()beforecommit()silently discards pending changes without raising warnings. - Accessing lazy-loaded relationships after
expunge()triggers immediateDetachedInstanceError. - Mixing
clear()with active transaction scopes causesInvalidRequestErroron subsequent queries. - Assuming
expunge()cascades to child objects (it does not; relationships remain tracked if not explicitly detached). - Using
clear()in long-polling async services without re-initializing the session causes stale connection references.
Frequently Asked Questions
Does Session.clear() trigger a database rollback?
No. clear() only empties the identity map. Pending changes remain in the active transaction until commit() or rollback() is explicitly invoked.
Can I use Session.expunge() with AsyncSession in SQLAlchemy 2.0?
Yes. AsyncSession delegates expunge() to the underlying synchronous Session. It operates synchronously on the identity map and does not require await.
When should I prefer clear() over expunge() in high-throughput APIs?
Use clear() at the end of stateless request handlers to guarantee zero memory retention. Use expunge() only when specific objects must persist in application cache outside the ORM session.
How do I resolve DetachedInstanceError after calling expunge()?
Pre-load required attributes and relationships before expunging using selectinload() or joinedload(), or re-attach the object via session.add() if further ORM operations are required.