SQLAlchemy 2.0: Async Without Blocking the Event Loop
SQLAlchemy 2.0 wraps its synchronous core with an async API, letting you `await` database calls without blocking your app's event loop. Use it in frameworks like FastAPI.
WHY IT EXISTS Traditional Python applications often used one thread per request, where a blocking database call was acceptable. Modern asyncio frameworks like FastAPI use a single event loop to handle many concurrent requests. A synchronous database call in this model would block the entire application, so a non-blocking way to interact with the database became essential.
THE MENTAL MODEL SQLAlchemy's async support is not a from-scratch rewrite; it's an adapter layer over its battle-tested synchronous core. You construct queries using the same powerful ORM and Core expressions. The difference is that all operations that perform I/O—like fetching results or committing a transaction—are now awaitable methods. It lets you write async code without learning a whole new query language.
HOW IT WORKS Instead of create_engine, you use create_async_engine with an async-compatible DBAPI driver (like asyncpg for PostgreSQL). You then use an async_sessionmaker to create AsyncSession objects. All I/O-bound methods on the session and result objects must be awaited: result = await session.execute(...), users = result.scalars().all(), and await session.commit(). Under the hood, SQLAlchemy manages running the synchronous driver code in a way that doesn't block the main asyncio event loop.
WHEN TO USE IT Use this whenever you are building an application on an asyncio framework like FastAPI, Starlette, or Quart. It is the standard for building high-concurrency Python services that need to talk to a relational database without creating performance bottlenecks from blocked I/O.
WHEN NOT TO USE IT If your application is purely synchronous (e.g., a simple Flask app, a command-line script, or a basic data analysis task), the async interface adds unnecessary complexity. Stick to the standard synchronous create_engine and Session. The performance benefit only materializes within an active asyncio event loop.
ONE CANONICAL EXAMPLE In a FastAPI app, you create a single async_sessionmaker at startup. For each incoming request, a dependency injection function yields a new AsyncSession. Your API endpoint function can then await database calls. The most common footgun is triggering implicit I/O. For example, accessing a lazy-loaded relationship like user.addresses without an explicit eager loading strategy (like selectinload) will cause a synchronous database query, blocking the event loop and defeating the entire purpose of using async.
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.