Cheatsheet for the engine/connection layer. Long-form: Textbook Ch 2 .
URL formats
# Postgres
"postgresql+asyncpg://user:pass@host/db" # async (recommended)
"postgresql+psycopg://user:pass@host/db" # psycopg v3 (sync or async)
"postgresql+psycopg2://user:pass@host/db" # legacy sync
# MySQL
"mysql+aiomysql://user:pass@host/db"
"mysql+asyncmy://user:pass@host/db"
"mysql+pymysql://user:pass@host/db"
# SQLite
"sqlite:///app.db"
"sqlite+aiosqlite:///app.db"
"sqlite:///:memory:"
Create engine
from sqlalchemy import create_engine
from sqlalchemy.ext.asyncio import create_async_engine
engine = create_engine(URL, echo=False)
engine = create_async_engine(URL, echo=False)
Pool params
engine = create_async_engine(
URL,
pool_size=20, # persistent connections
max_overflow=10, # extra under load
pool_timeout=30, # wait this long for free conn
pool_pre_ping=True, # ping on checkout (catch dead conns)
pool_recycle=3600, # recycle conns > N seconds
echo=False,
echo_pool=False,
future=True,
)
Pool classes
from sqlalchemy.pool import NullPool, StaticPool, QueuePool
# No pool (serverless)
engine = create_engine(URL, poolclass=NullPool)
# Single shared connection (SQLite in-memory tests)
engine = create_engine(
"sqlite://",
poolclass=StaticPool,
connect_args={"check_same_thread": False},
)
Async default is AsyncAdaptedQueuePool.
connect_args
# asyncpg
engine = create_async_engine(URL, connect_args={
"server_settings": {
"application_name": "myapp",
"search_path": "myapp,public",
"timezone": "UTC",
},
"command_timeout": 60,
"statement_cache_size": 1000,
})
# psycopg v3
engine = create_async_engine(URL, connect_args={
"options": "-c search_path=myapp,public -c application_name=myapp",
})
Connection usage
# Sync
with engine.connect() as conn:
result = conn.execute(text("SELECT 1"))
print(result.scalar())
with engine.begin() as conn: # auto-commit on exit
conn.execute(text("CREATE TABLE ..."))
# Async
async with engine.connect() as conn:
result = await conn.execute(text("SELECT 1"))
async with engine.begin() as conn:
await conn.execute(text("..."))
Pool stats
print(engine.pool.status())
print(engine.pool.size(), engine.pool.checked_out())
Expose as Prometheus gauges.
Events
from sqlalchemy import event
@event.listens_for(engine.sync_engine, "connect")
def on_connect(dbapi_conn, conn_record):
cur = dbapi_conn.cursor()
cur.execute("SET TIME ZONE 'UTC'")
cur.execute("SET statement_timeout = '30s'")
cur.close()
@event.listens_for(engine.sync_engine, "checkout")
def on_checkout(dbapi_conn, conn_record, conn_proxy):
...
Slow query log
import time
@event.listens_for(engine.sync_engine, "before_cursor_execute")
def before(conn, cur, stmt, params, ctx, executemany):
ctx._t = time.time()
@event.listens_for(engine.sync_engine, "after_cursor_execute")
def after(conn, cur, stmt, params, ctx, executemany):
dur = time.time() - ctx._t
if dur > 0.5:
log.warning("slow_query", sql=stmt[:500], ms=dur*1000)
Lifespan with FastAPI
@asynccontextmanager
async def lifespan(app):
app.state.engine = create_async_engine(URL, pool_size=20, pool_pre_ping=True)
app.state.sm = async_sessionmaker(app.state.engine, expire_on_commit=False)
yield
await app.state.engine.dispose()
async def get_db(request: Request) -> AsyncSession:
async with request.app.state.sm() as session:
yield session
Multiple engines (read replicas)
write_engine = create_async_engine(WRITER_URL, pool_size=20)
read_engine = create_async_engine(
READER_URL,
pool_size=40,
connect_args={"server_settings": {"default_transaction_read_only": "on"}},
)
Dispose
await engine.dispose() # close all pooled connections
Always in lifespan shutdown.
Cross-DB notes
- Postgres:
max_connectionsmatters; PgBouncer for scale. - MySQL: similar; use
connection_attributesfor tracing. - SQLite: file-based; one writer at a time; pool less relevant.
Common mistakes
- Engine per request (should be process-singleton).
pool_sizetoo large vsmax_connections.- Missing
pool_pre_ping(stale-conn errors after restart). - Holding sessions across long awaits (pool starvation).
Read this next
If you want my engine + lifespan + pool-metrics starter, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .