Home Python Async SQLAlchemy: asyncpg, Alembic, and Production Patterns
Advanced 5 min · July 14, 2026

Async SQLAlchemy: asyncpg, Alembic, and Production Patterns

Master async SQLAlchemy with asyncpg and Alembic.

N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Written from production experience, not tutorials.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of Python and asyncio
  • Familiarity with SQLAlchemy ORM basics
  • PostgreSQL database installed and running
  • Python 3.7+
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Use async SQLAlchemy with asyncpg for non-blocking database access in async apps.
  • Configure async engine with create_async_engine and use AsyncSession for transactions.
  • Alembic supports async migrations via run_async() in env.py.
  • Production patterns include connection pooling, retry logic, and proper session management.
  • Avoid mixing sync and async code; use asyncio.gather for concurrent queries.
✦ Definition~90s read
What is Async SQLAlchemy?

Async SQLAlchemy is the asynchronous version of the SQLAlchemy ORM, allowing you to perform non-blocking database operations using Python's asyncio framework with asyncpg for PostgreSQL.

Imagine a restaurant where orders come in continuously.
Plain-English First

Imagine a restaurant where orders come in continuously. A sync waiter handles one order at a time, blocking others. An async waiter can take multiple orders simultaneously, juggling tasks without waiting. Async SQLAlchemy with asyncpg is like having that async waiter for your database—allowing your app to handle many database requests concurrently without slowing down.

In modern web development, asynchronous programming is essential for building high-performance applications that can handle thousands of concurrent connections. Python's asyncio ecosystem, combined with async database drivers like asyncpg, enables non-blocking database operations. SQLAlchemy, the leading ORM for Python, now offers full async support, allowing you to use the same powerful ORM patterns in async contexts.

This tutorial dives deep into async SQLAlchemy with asyncpg, covering setup, query patterns, Alembic migrations, and production-ready practices. You'll learn how to configure an async engine, manage sessions, handle transactions, and avoid common pitfalls. We'll also explore real-world scenarios like connection pooling, retry logic, and debugging async database issues.

By the end, you'll be equipped to build scalable async applications that interact with PostgreSQL efficiently, using SQLAlchemy's familiar API in a non-blocking way. Whether you're building a FastAPI service, an aiohttp app, or any asyncio-based system, these patterns will help you write robust, maintainable database code.

Setting Up Async SQLAlchemy with asyncpg

To get started, install the required packages: pip install sqlalchemy[asyncio] asyncpg. The async engine is created using create_async_engine instead of the sync create_engine. This engine uses asyncpg under the hood to communicate with PostgreSQL asynchronously.

```python from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker

DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/dbname"

engine = create_async_engine(DATABASE_URL, echo=True, pool_size=5, max_overflow=10)

async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) ```

Key parameters
  • pool_size: Number of connections to maintain in the pool.
  • max_overflow: Maximum number of connections that can be created beyond pool_size.
  • echo: Logs all SQL statements (useful for debugging).
  • expire_on_commit=False: Prevents objects from being expired after commit, which can avoid lazy loads in async contexts.

``python async with async_session() as session: result = await session.execute(select(User).where(User.id == 1)) user = result.scalar_one() ``

async_setup.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy import Column, Integer, String, select

DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/testdb"

engine = create_async_engine(DATABASE_URL, echo=True, pool_size=5, max_overflow=10)

async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))

async def get_user(user_id: int):
    async with async_session() as session:
        result = await session.execute(select(User).where(User.id == user_id))
        return result.scalar_one()
💡Always use async context managers
📊 Production Insight
Set pool_pre_ping=True to verify connections before use, preventing stale connections from causing errors.
🎯 Key Takeaway
Use create_async_engine with asyncpg URL and AsyncSession for non-blocking database access.

Defining Models and Running Queries

Models are defined similarly to sync SQLAlchemy, using declarative_base. However, all database operations must be awaited. Use select() from SQLAlchemy's core to build queries.

```python from sqlalchemy import select

async def get_users(): async with async_session() as session: stmt = select(User).where(User.name.like('A%')) result = await session.execute(stmt) users = result.scalars().all() return users ```

For relationships, use selectinload to eagerly load related objects and avoid N+1 queries:

```python from sqlalchemy.orm import selectinload

stmt = select(User).options(selectinload(User.posts)) result = await session.execute(stmt) ```

```python from sqlalchemy import text

result = await session.execute(text("SELECT * FROM users WHERE id = :id"), {"id": 1}) ```

Remember that all operations that touch the database must be awaited, including commit(), rollback(), flush(), and refresh().

async_queries.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sqlalchemy import select, text
from sqlalchemy.orm import selectinload

async def get_user_with_posts(user_id: int):
    async with async_session() as session:
        stmt = select(User).options(selectinload(User.posts)).where(User.id == user_id)
        result = await session.execute(stmt)
        return result.scalar_one()

async def create_user(name: str):
    async with async_session() as session:
        user = User(name=name)
        session.add(user)
        await session.commit()
        await session.refresh(user)
        return user

async def raw_query():
    async with async_session() as session:
        result = await session.execute(text("SELECT count(*) FROM users"))
        count = result.scalar()
        return count
⚠ Avoid mixing sync and async sessions
📊 Production Insight
Use await session.refresh(user) after commit to get database-generated values like auto-increment IDs.
🎯 Key Takeaway
All database operations must be awaited. Use selectinload for eager loading to prevent N+1 queries.

Transactions and Session Management

Async SQLAlchemy provides two ways to manage transactions: automatic commit on context exit and explicit transaction blocks.

Automatic commit: When using async with async_session() as session:, the session will automatically commit if no exception occurs, otherwise it rolls back. This is the recommended pattern for simple operations.

Explicit transaction: Use async with session.begin(): to control the transaction boundaries manually. This is useful when you need to perform multiple operations in a single transaction:

``python async with async_session() as session: async with session.begin(): user = User(name='Alice') session.add(user) # other operations # transaction is committed here ``

You can also use await session.commit() and await session.rollback() explicitly, but be careful to always close the session.

Nested transactions are supported via savepoints:

``python async with session.begin_nested(): # this is a savepoint session.add(some_object) ``

In production, always handle exceptions and ensure sessions are closed. Use try/finally or context managers.

transactions.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from sqlalchemy.exc import SQLAlchemyError

async def transfer_funds(sender_id: int, receiver_id: int, amount: float):
    async with async_session() as session:
        try:
            async with session.begin():
                sender = await session.get(User, sender_id)
                receiver = await session.get(User, receiver_id)
                sender.balance -= amount
                receiver.balance += amount
        except SQLAlchemyError as e:
            print(f"Transaction failed: {e}")
            raise
        # session is automatically closed

async def explicit_commit():
    session = async_session()
    try:
        user = User(name='Bob')
        session.add(user)
        await session.commit()
    except Exception:
        await session.rollback()
        raise
    finally:
        await session.close()
🔥Session per request pattern
📊 Production Insight
Set a statement timeout on the engine to prevent long-running queries from blocking the pool: create_async_engine(..., connect_args={"statement_timeout": 30000}) (30 seconds).
🎯 Key Takeaway
Use async with session.begin() for explicit transaction control. Always ensure sessions are closed.

Alembic Migrations for Async Databases

Alembic supports async migrations with a few configuration changes. First, install alembic: pip install alembic. Then initialize: alembic init alembic.

```python from sqlalchemy.ext.asyncio import create_async_engine from alembic import context

async def run_migrations_online(): connectable = create_async_engine(DATABASE_URL) async with connectable.connect() as connection: await connection.run_sync(do_run_migrations) await connectable.dispose()

def do_run_migrations(connection): context.configure(connection=connection, target_metadata=Base.metadata) with context.begin_transaction(): context.run_migrations()

def run_migrations_online(): asyncio.run(run_migrations_online()) ```

Note: The run_async() method is available in newer Alembic versions. Alternatively, use asyncio.run() as shown.

For offline mode, you can generate SQL scripts synchronously.

When creating migrations, use alembic revision --autogenerate -m "message". The autogenerate will detect model changes and generate the migration script. However, be cautious with async-specific features like async def in migration scripts—avoid them unless necessary.

alembic/env.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from logging.config import fileConfig
from sqlalchemy.ext.asyncio import create_async_engine
from alembic import context
import asyncio

config = context.config
fileConfig(config.config_file_name)

from app.models import Base  # your models

target_metadata = Base.metadata

DATABASE_URL = config.get_main_option("sqlalchemy.url")

def run_migrations_offline():
    context.configure(url=DATABASE_URL, target_metadata=target_metadata, literal_binds=True)
    with context.begin_transaction():
        context.run_migrations()

async def run_migrations_online():
    connectable = create_async_engine(DATABASE_URL)
    async with connectable.connect() as connection:
        await connection.run_sync(do_run_migrations)
    await connectable.dispose()

def do_run_migrations(connection):
    context.configure(connection=connection, target_metadata=target_metadata)
    with context.begin_transaction():
        context.run_migrations()

if context.is_offline_mode():
    run_migrations_offline()
else:
    asyncio.run(run_migrations_online())
💡Use `run_async()` in newer Alembic
📊 Production Insight
Always test migrations on a staging database first. Use alembic upgrade --sql to generate SQL for review before applying.
🎯 Key Takeaway
Configure Alembic to use async engine by wrapping the online migration in asyncio.run() or using run_async().

Connection Pooling and Performance Tuning

Async SQLAlchemy uses a connection pool managed by asyncpg. Proper configuration is critical for performance.

Key pool settings
  • pool_size: Number of persistent connections (default 5).
  • max_overflow: Maximum temporary connections beyond pool_size (default 10).
  • pool_timeout: Seconds to wait for a connection from the pool (default 30).
  • pool_recycle: Seconds after which a connection is recycled (default -1, no recycle).
  • pool_pre_ping: If True, verifies connections before use (recommended).

Example: ``python engine = create_async_engine( DATABASE_URL, pool_size=10, max_overflow=20, pool_timeout=60, pool_recycle=3600, pool_pre_ping=True ) ``

For high-concurrency apps, consider using a larger pool size. Monitor pool usage with: ``python engine.pool.status() # returns a dict with pool info ``

Query optimization: Use selectinload for relationships, avoid lazy loading in async contexts. Use limit and offset for pagination. Use execution_options to set statement timeout per query: ``python result = await session.execute(stmt.execution_options(statement_timeout=5000)) ``

Batch operations: Use insert().returning() for bulk inserts: ``python from sqlalchemy import insert stmt = insert(User).returning(User.id, User.name) result = await session.execute(stmt, [{"name": "Alice"}, {"name": "Bob"}]) ``

pool_config.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from sqlalchemy.ext.asyncio import create_async_engine

engine = create_async_engine(
    "postgresql+asyncpg://user:pass@localhost/db",
    pool_size=10,
    max_overflow=20,
    pool_timeout=60,
    pool_recycle=3600,
    pool_pre_ping=True,
    echo=False
)

# Check pool status
print(engine.pool.status())

# Example of batch insert with returning
from sqlalchemy import insert

async def bulk_create_users(names: list):
    async with async_session() as session:
        stmt = insert(User).returning(User.id, User.name)
        result = await session.execute(stmt, [{"name": name} for name in names])
        await session.commit()
        return result.fetchall()
⚠ Avoid connection leaks
📊 Production Insight
Set pool_recycle to less than the database's wait_timeout to prevent connection drops. For AWS RDS, 3600 seconds is common.
🎯 Key Takeaway
Configure pool size based on expected concurrency. Use pool_pre_ping and monitor pool status.

Error Handling and Retry Logic

In production, transient errors like connection timeouts, deadlocks, and serialization failures are common. Implement retry logic with exponential backoff.

```python from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type from sqlalchemy.exc import OperationalError, DeadlockError

@retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10), retry=retry_if_exception_type((OperationalError, DeadlockError)) ) async def execute_with_retry(session, stmt): return await session.execute(stmt) ```

For transaction retries, you need to restart the entire transaction:

```python from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10)) async def transfer_with_retry(sender_id, receiver_id, amount): async with async_session() as session: async with session.begin(): sender = await session.get(User, sender_id) receiver = await session.get(User, receiver_id) sender.balance -= amount receiver.balance += amount ```

Note: The retry decorator will re-execute the entire function on failure, creating a new session.

Handling specific exceptions
  • OperationalError: Connection issues, timeouts.
  • DeadlockError: Deadlock detected.
  • IntegrityError: Constraint violations (usually not retryable).

Always log exceptions for debugging.

retry_logic.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
from sqlalchemy.exc import OperationalError, DeadlockError

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=1, max=10),
    retry=retry_if_exception_type((OperationalError, DeadlockError))
)
async def safe_transfer(sender_id: int, receiver_id: int, amount: float):
    async with async_session() as session:
        async with session.begin():
            sender = await session.get(User, sender_id)
            receiver = await session.get(User, receiver_id)
            sender.balance -= amount
            receiver.balance += amount

# Usage
await safe_transfer(1, 2, 100.0)
🔥Don't retry non-retryable errors
📊 Production Insight
Set a global statement timeout on the engine to prevent queries from hanging indefinitely, and use retries for timeouts.
🎯 Key Takeaway
Implement retry logic with exponential backoff for transient database errors. Use tenacity for simplicity.

Testing Async Database Code

Testing async database code requires async test runners like pytest-asyncio. Install: pip install pytest-asyncio.

```python import pytest from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker

@pytest.fixture async def engine(): engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/testdb") async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine async with engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) await engine.dispose()

@pytest.fixture async def session(engine): async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async with async_session() as session: yield session

@pytest.mark.asyncio async def test_create_user(session): user = User(name="Test") session.add(user) await session.commit() result = await session.execute(select(User).where(User.name == "Test")) assert result.scalar_one().name == "Test" ```

Use pytest.mark.asyncio decorator for async tests. For mocking, use unittest.mock with async mocks.

Isolation: Use separate test database or transactions that roll back after each test. The fixture above creates tables before and drops after.

test_async_db.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pytest
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from sqlalchemy import select

@pytest.fixture
async def engine():
    engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/testdb")
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield engine
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)
    await engine.dispose()

@pytest.fixture
async def session(engine):
    async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
    async with async_session() as session:
        yield session

@pytest.mark.asyncio
async def test_create_user(session):
    user = User(name="Alice")
    session.add(user)
    await session.commit()
    result = await session.execute(select(User).where(User.name == "Alice"))
    assert result.scalar_one().name == "Alice"
💡Use rollback-based cleanup
📊 Production Insight
Run tests in parallel using pytest-xdist with --asyncio-mode=auto to speed up test suites.
🎯 Key Takeaway
Use pytest-asyncio for async tests. Create fixtures for engine and session with proper setup/teardown.

Production Patterns and Best Practices

Deploying async SQLAlchemy in production requires attention to several patterns:

1. Session per request: In web frameworks, create a session per request and close it after the response. Use dependency injection (e.g., FastAPI's Depends).

2. Use connection pooling wisely: Set pool size based on your database's max connections and application concurrency. Monitor with SELECT * FROM pg_stat_activity.

3. Graceful shutdown: Dispose the engine on app shutdown to close all connections: ``python async def shutdown(): await engine.dispose() ``

4. Health checks: Implement a health check endpoint that runs a simple query: ``python async def health_check(): async with async_session() as session: await session.execute(select(1)) return {"status": "ok"} ``

5. Use environment variables for database URLs, never hardcode.

6. Logging: Enable SQLAlchemy logging in development, but disable in production to avoid performance overhead. Use structured logging.

7. Avoid global sessions: Never share sessions across requests or tasks. Each coroutine should have its own session.

8. Use asyncio.gather for concurrent queries: When you need multiple independent queries, run them concurrently: ``python async def get_dashboard_data(user_id): async with async_session() as session: user_task = session.get(User, user_id) posts_task = session.execute(select(Post).where(Post.user_id == user_id)) user, posts = await asyncio.gather(user_task, posts_task) return user, posts.scalars().all() ``

production_patterns.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import asyncio
from sqlalchemy import select

async def get_dashboard_data(user_id: int):
    async with async_session() as session:
        user_task = session.get(User, user_id)
        posts_task = session.execute(select(Post).where(Post.user_id == user_id))
        user, posts = await asyncio.gather(user_task, posts_task)
    return user, posts.scalars().all()

# FastAPI dependency example
from fastapi import Depends, FastAPI

app = FastAPI()

async def get_session():
    async with async_session() as session:
        yield session

@app.get("/users/{user_id}")
async def get_user(user_id: int, session: AsyncSession = Depends(get_session)):
    result = await session.execute(select(User).where(User.id == user_id))
    return result.scalar_one()
⚠ Avoid mixing asyncio.gather with session sharing
📊 Production Insight
Set pool_size to the number of concurrent requests your app handles, plus a small buffer. Monitor with SELECT count(*) FROM pg_stat_activity WHERE state = 'active'.
🎯 Key Takeaway
Use session per request, dispose engine on shutdown, and leverage asyncio.gather for concurrent queries.
● Production incidentPOST-MORTEMseverity: high

The Silent Connection Pool Exhaustion

Symptom
After a few hours of normal operation, the app started throwing 'TimeoutError: could not acquire connection from pool' errors. New requests hung indefinitely.
Assumption
The developer assumed that async sessions would be automatically garbage-collected and connections returned to the pool.
Root cause
Async sessions were created but never explicitly closed (or used as context managers). Each leaked session held a connection from the pool, eventually exhausting it.
Fix
Ensured every session was used with async with AsyncSession(engine) as session: or explicitly closed with await session.close(). Added monitoring for pool usage.
Key lesson
  • Always use async context managers for sessions to guarantee cleanup.
  • Monitor connection pool metrics (size, overflow, wait time) in production.
  • Implement retry logic with exponential backoff for transient pool timeouts.
  • Set a reasonable pool size and overflow limit based on expected concurrency.
  • Use pool_pre_ping=True to detect stale connections.
Production debug guideSymptom to Action4 entries
Symptom · 01
Connection pool timeout
Fix
Check for unclosed sessions; verify pool size and overflow settings; use pool_pre_ping=True.
Symptom · 02
Slow queries under load
Fix
Enable SQLAlchemy echo logging; check for N+1 queries; use selectinload for eager loading.
Symptom · 03
Alembic migration hangs
Fix
Ensure migration script uses run_async(); check for long-running transactions; set a statement timeout.
Symptom · 04
Deadlock errors
Fix
Review transaction isolation levels; ensure consistent ordering of table access; use SELECT ... FOR UPDATE carefully.
★ Quick Debug Cheat SheetCommon async SQLAlchemy issues and immediate actions.
Cannot acquire connection from pool
Immediate action
Check for unclosed sessions
Commands
SELECT count(*) FROM pg_stat_activity WHERE state = 'idle in transaction';
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle in transaction';
Fix now
Wrap all session usage in async with blocks.
Alembic migration fails with 'No async connection'+
Immediate action
Update env.py to use run_async()
Commands
Check alembic/env.py for async configuration
Ensure `asyncio.run(engine.dispose())` is called
Fix now
Use context.run_async() and async_engine.connect() in migration scripts.
Query returns stale data+
Immediate action
Check transaction isolation level
Commands
SHOW default_transaction_isolation;
Check if session is using autocommit
Fix now
Set isolation level to READ COMMITTED or use with session.begin() for explicit transactions.
FeatureSync SQLAlchemyAsync SQLAlchemy
Engine creationcreate_enginecreate_async_engine
Session classSessionAsyncSession
Query executionsession.execute(stmt)await session.execute(stmt)
Transactionsession.begin()async with session.begin()
Connection poolQueuePoolAsyncAdaptedQueuePool
Driverpsycopg2, pymysqlasyncpg, aiomysql
⚙ Quick Reference
8 commands from this guide
FileCommand / CodePurpose
async_setup.pyfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSessionSetting Up Async SQLAlchemy with asyncpg
async_queries.pyfrom sqlalchemy import select, textDefining Models and Running Queries
transactions.pyfrom sqlalchemy.exc import SQLAlchemyErrorTransactions and Session Management
alembicenv.pyfrom logging.config import fileConfigAlembic Migrations for Async Databases
pool_config.pyfrom sqlalchemy.ext.asyncio import create_async_engineConnection Pooling and Performance Tuning
retry_logic.pyfrom tenacity import retry, stop_after_attempt, wait_exponential, retry_if_excep...Error Handling and Retry Logic
test_async_db.pyfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSessionTesting Async Database Code
production_patterns.pyfrom sqlalchemy import selectProduction Patterns and Best Practices

Key takeaways

1
Use create_async_engine with asyncpg for non-blocking database access.
2
Manage sessions with async with to ensure proper cleanup.
3
Configure Alembic for async migrations using asyncio.run() or run_async().
4
Implement retry logic for transient errors and monitor connection pool usage.
5
Use selectinload to avoid N+1 queries and asyncio.gather for concurrent operations.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
How do you configure an async SQLAlchemy engine with asyncpg?
Q02SENIOR
Explain how to handle transactions in async SQLAlchemy.
Q03SENIOR
What are common pitfalls when using async SQLAlchemy in production?
Q01 of 03JUNIOR

How do you configure an async SQLAlchemy engine with asyncpg?

ANSWER
Use create_async_engine with a URL like postgresql+asyncpg://user:pass@host/db. Set pool_size, max_overflow, and other parameters. Then create an AsyncSession class using sessionmaker.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Can I use async SQLAlchemy with databases other than PostgreSQL?
02
How do I handle migrations with async SQLAlchemy?
03
What is the difference between `AsyncSession` and `Session`?
04
How do I debug async SQLAlchemy queries?
05
Can I use sync and async SQLAlchemy in the same project?
N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Written from production experience, not tutorials.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Python Libraries. Mark it forged?

5 min read · try the examples if you haven't

Previous
Dask: Parallel Computing for Large Datasets
61 / 69 · Python Libraries
Next
Alembic: Database Migration Management for SQLAlchemy