FastAPI: From Basics to Production · Databases with SQLAlchemy 2.0 and Alembic
Relationships and Avoiding N+1 Queries
Eager-load related data explicitly in async SQLAlchemy.
Lazy loading doesn't work implicitly with async sessions, and in sync code it causes N+1 queries. Load relationships explicitly with selectinload (a second IN query — best for collections) or joinedload (a JOIN — good for many-to-one). Set lazy="raise" on relationships to fail fast when something isn't eagerly loaded.
from sqlalchemy import select
from sqlalchemy.orm import joinedload, selectinload
async def orders_with_items(db: AsyncSession, user_id: int) -> list[Order]:
stmt = (
select(Order)
.where(Order.user_id == user_id)
.options(
selectinload(Order.items).joinedload(OrderItem.product), # collection + many-to-one
joinedload(Order.user),
)
)
return list(await db.scalars(stmt))
class Order(Base):
# ...
items: Mapped[list["OrderItem"]] = relationship(lazy="raise") # fail fast if not eager-loaded
Common pitfalls
MissingGreenleterrors from accessing unloaded relationships in async code.- joinedload on large collections, multiplying rows.
- Serializing nested responses without loading their relationships.
Key points
- selectinload for collections
- joinedload for many-to-one
- lazy='raise' surfaces mistakes
Further reading: SQLAlchemy: Relationship loading
Practice questions
When would you choose selectinload over joinedload?
Answer
For one-to-many collections: selectinload issues one extra IN query and avoids duplicating parent rows that a JOIN would produce.
What does lazy='raise' do?
Answer
Accessing the relationship without eager loading raises an error immediately, catching N+1 problems and async lazy-load errors during development.
Open in the app to mark lessons complete, take notes and track your progress.