tezvyn:

SQLAlchemy: Control When Your Relationships Load

AI-drafted, machine-checkedSource: docs.sqlalchemy.orgadvanced

SQLAlchemy's default lazy loading is convenient but can cause an N+1 query storm. Use eager loading (`joinedload`, `selectinload`) for collections you'll access to prevent many database round trips.

WHY IT EXISTS An ORM maps database rows to objects. A User object might relate to a list of Address objects. The core problem is deciding when to fetch those addresses. Fetching them all upfront can be slow and wasteful if they aren't used. Fetching them one-by-one on demand can lead to the infamous N+1 query problem. Relationship loading strategies give you explicit control over this trade-off.

THE MENTAL MODEL Think of it as packing for a trip. Lazy loading is packing light and buying things as you need them—a fast departure, but many small, slow trips to the store later. Eager loading is packing everything you anticipate needing upfront—a slower departure, but no extra trips. The right choice depends on what you plan to do at your destination.

HOW IT WORKS SQLAlchemy offers several strategies, specified via query options. The default is lazy='select', which issues a new SELECT statement the first time you access a related attribute on an object. This is the source of the N+1 problem. Eager loading strategies fetch the data upfront. joinedload() uses a SQL JOIN in the main query to fetch parent and child objects together. selectinload() issues a second query using the primary keys from the first query to fetch all related objects at once. raiseload() is a guard: it prevents access entirely by throwing an error, stopping accidental lazy loads that would harm performance.

WHEN TO USE IT Use eager loading (joinedload or selectinload) when you know you will access the related objects for every parent object in your result set, especially inside a loop. This is the classic fix for the N+1 query problem. Use raiseload in APIs to enforce performance contracts and prevent unexpected database hits from code changes.

WHEN NOT TO USE IT Don't use eager loading if you only occasionally access the related objects; the upfront cost of the larger query isn't worth it. Avoid joinedload for large to-many collections, as the JOIN can create a Cartesian product, returning many redundant rows from the database and consuming excess memory. In these cases, selectinload is almost always a better choice for to-many relationships.

ONE CANONICAL EXAMPLE A common bug is fetching users and displaying their addresses: for user in users: print(user.addresses). This triggers one query for users, plus N additional queries for each user's addresses. Using select(User).options(selectinload(User.addresses)) fetches all users in one query and all their addresses in a single second query, eliminating the N+1 storm.

Read the original → docs.sqlalchemy.org

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.