Building Non‑Blocking Data Layers with SQLAlchemy 2.0 AsyncIO
Learn how to set up SQLAlchemy 2.0’s AsyncEngine and AsyncSession for non‑blocking database access in FastAPI, including a working example, verification steps, and key limitations.
07 Aug 2025, 06:18 UTC

Problem: Synchronous DB Calls Stall Async Web Apps
When you write a FastAPI (or any async Python) endpoint that talks to a relational database, a blocking DB call holds up the event loop. Even a single slow query can degrade throughput for all concurrent requests.
Thesis: SQLAlchemy 2.0’s AsyncEngine and AsyncSession let you keep the familiar ORM API while moving I/O off the event loop.
1. Creating an Async Engine
SQLAlchemy 2.0 introduces AsyncEngine and AsyncSession. To use them you need an async‑compatible DBAPI driver. For PostgreSQL the popular choice is asyncpg. The database URL must use the postgresql+asyncpg:// prefix.
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
# Replace with your actual credentials
DATABASE_URL = "postgresql+asyncpg://user:password@localhost/dbname"
engine = create_async_engine(DATABASE_URL, echo=False, future=True)
AsyncSessionLocal = sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False,
autoflush=False,
)
2. Using the Async Session in a FastAPI Endpoint
The async session mirrors the synchronous pattern but requires async with and await for query execution. Below is a minimal FastAPI app that returns a list of users.
from fastapi import FastAPI, Depends
from sqlalchemy import select
from models import User # Assume a declarative User model
app = FastAPI()
async def get_session() -> AsyncSession:
async with AsyncSessionLocal() as session:
yield session
@app.get("/users")
async def read_users(session: AsyncSession = Depends(get_session)):
result = await session.execute(select(User))
users = result.scalars().all()
return [{"id": u.id, "name": u.name} for u in users]
To run the example:
- Install the required packages (Python 3.11+ recommended):
pip install 'sqlalchemy[asyncio]' asyncpg fastapi uvicorn - Save the code above to
main.py(ensure yourmodels.pydefines theUsertable). - Start the server:
uvicorn main:app --reload - Call the endpoint, e.g.
curl http://127.0.0.1:8000/users. You should see a JSON array of user objects.
3. Verifying Non‑Blocking Behavior
Add a short sleep to the endpoint to simulate work that would block a synchronous call:
@app.get("/users")
async def read_users(session: AsyncSession = Depends(get_session)):
await asyncio.sleep(0.1) # yields control back to the event loop
result = await session.execute(select(User))
# ... same as before
Open two terminals and run:
# Terminal 1
while true; do curl -s http://127.0.0.1:8000/users > /dev/null; done
# Terminal 2 (in another shell)
while true; do curl -s http://127.0.0.1:8000/users > /dev/null; done
If the endpoint were blocking, the second stream would noticeably lag. With the async setup both streams continue to receive responses promptly, demonstrating that the event loop stays free.
4. Trade‑offs and Limitations
- Driver requirement: Only async‑compatible DB‑APIs work (e.g.,
asyncpgfor PostgreSQL,aiomysqlfor MySQL). Using a sync driver withAsyncEngineraises an error or silently falls back to blocking I/O. - ORM feature coverage: Some legacy ORM constructs that rely on synchronous attribute tracking (e.g., mutable scalar attributes with custom setters) are not supported in the async session. Review the SQLAlchemy 2.0 migration guide for details.
- Echo and debugging: Setting
echo=Trueon anAsyncEnginelogs SQL as usual, but the log output may interleave with other async tasks; consider using structured logging if needed.
Actionable Closing
If you are building an async Python service and need relational data access, start by:
- Choosing an async DBAPI driver that matches your database.
- Creating an
AsyncEnginewith the+asyncpg://(or equivalent) URL scheme. - Using
AsyncSessionLocalwithasync withandawaitfor all queries. - Verifying non‑blocking behavior with a simple sleep test under load.
With these steps you retain the productivity of SQLAlchemy’s ORM while gaining the scalability benefits of true async I/O.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.