Async Databases
Async database access keeps the event loop free while waiting on the server. asyncpg (PostgreSQL), SQLAlchemy 2 async, and driver-specific pools integrate with FastAPI lifespan and per-request sessions.
Search across all documentation pages
Async database access keeps the event loop free while waiting on the server. asyncpg (PostgreSQL), SQLAlchemy 2 async, and driver-specific pools integrate with FastAPI lifespan and per-request sessions.
# Conceptual SQLAlchemy 2 async + asyncpg
# engine = create_async_engine("postgresql+asyncpg://...")
# async with AsyncSession(engine) as session:
# result = await session.execute(select(User).limit(1))When to reach for this:
async def routesSimulated asyncpg-style flow with asyncio stand-ins for runnable demo without live DB.
import asyncio
from dataclasses import dataclass, field
@dataclass
class FakePool:
_connections: int = 0
max_size: int = 5
async def acquire(self) -> "FakeConn":
await asyncio.sleep(0.01)
self._connections += 1
return FakeConn(self)
async def release(self, conn: "FakeConn") -> None:
await asyncio.sleep(0)
self._connections -= 1
@dataclass
class FakeConn:
pool: FakePool
async def fetchval(self, query: str) -> int:
await asyncio.sleep(0.02)
return 42
class DB:
def __init__(self, pool: FakePool) -> None:
self._pool = pool
async def user_count(self) -> int:
conn = await self._pool.acquire()
try:
return await conn.fetchval("SELECT COUNT(*) FROM users")
finally:
await self._pool.release(conn)
async def main() -> None:
pool = FakePool()
db = DB(pool)
counts = await asyncio.gather(*(db.user_count() for _ in range(3)))
print(counts, "pool conns", pool._connections)
asyncio.run(main())What this demonstrates:
finally ensures connection returns to pool| Layer | PostgreSQL |
|---|---|
| Driver | asyncpg |
| ORM | SQLAlchemy 2 AsyncSession |
| Migrations | Alembic (sync engine) separate job |
AsyncSession + await execute.| Alternative | Use When | Don't Use When |
|---|---|---|
| Sync ORM + thread pool | Gradual migration | Greenfield async API |
| Raw asyncpg | Max perf, SQL control | Heavy ORM needs |
| DynamoDB async SDK | AWS NoSQL | Relational needs |
Both viable; SQLAlchemy abstracts driver - pick per ops familiarity and feature needs.
(expected_concurrent_requests * avg_query_time) / target_latency bounded by DB max_connections.
create_async_engine, async_sessionmaker, async with session.begin().
Alembic typically runs sync migration job in CI - not in request hot path.
aiosqlite for dev/small apps - different concurrency limits than Postgres.
Improving - check Django 5.2 docs for async query support on your models.
Testcontainers Postgres or sqlite+aiosqlite with pytest-asyncio fixtures.
Always context-manage sessions; monitor pool checked-out count metric.
Separate engines/pools for read vs write routing in app layer.
model_validate on dict rows from mapping().all() - validate at boundary.
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 19, 2026