Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/launchbadge/sqlx/llms.txt

Use this file to discover all available pages before exploring further.

A connection pool maintains a set of open database connections and lends them out to your application as needed. Rather than opening and closing a connection for every query — which involves DNS resolution, TCP handshakes, authentication, and query plan cache warm-up — a pool reuses existing connections, amortizing those costs across many operations. SQLx ships with a production-ready async pool via sqlx::Pool.

Why use a pool?

Opening a new database connection is expensive. For server-based databases like PostgreSQL and MySQL, the server typically spawns a new thread or process for each connection and enforces a hard connection limit (100 for Postgres by default, 150 for MySQL). Exceeding this limit causes errors for new connections. A pool solves both problems: it reuses connections to avoid per-connection overhead, and it queues acquire() calls in a fair, first-come-first-serve order when all connections are in use — preventing your application from exceeding the server’s connection limit under load.

Creating a pool

The simplest way to create a pool is Pool::connect(), which opens one connection immediately to verify the configuration:
use sqlx::PgPool;

let pool = PgPool::connect("postgres://alice:secret@localhost/mydb").await?;
For more control, use PoolOptions:
use sqlx::postgres::PgPoolOptions;

let pool = PgPoolOptions::new()
    .max_connections(20)
    .connect("postgres://alice:secret@localhost/mydb")
    .await?;
To defer opening any connections until the first acquire() call, use connect_lazy():
let pool = PgPool::connect_lazy("postgres://localhost/mydb")?;

Per-database pool type aliases

SQLx provides type aliases so you can use a concrete type rather than Pool<Postgres> throughout your codebase:

PgPool

sqlx::postgres::PgPool — PostgreSQL

MySqlPool

sqlx::mysql::MySqlPool — MySQL / MariaDB

SqlitePool

sqlx::sqlite::SqlitePool — SQLite

AnyPool

sqlx::any::AnyPool — runtime-selected driver
All are type aliases for Pool<DB> and share the same API.

Using a pool

Pool implements Executor, so you can pass &pool directly to any query finalizer. SQLx automatically acquires a connection from the pool, runs your query, and returns the connection when the future resolves:
// Pass &pool directly — no manual acquire() needed
let todos: Vec<Todo> = sqlx::query_as::<_, Todo>(
    "SELECT id, description, done FROM todos ORDER BY id"
)
.fetch_all(&pool)
.await?;
Pool is Clone, Send, and Sync. Create it once and share it across your application — clone it into request handlers, pass it to spawned tasks, or store it in application state:
use sqlx::PgPool;
use std::env;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let pool = PgPool::connect(&env::var("DATABASE_URL")?).await?;

    // Clone cheaply — all clones share the same underlying pool
    let pool_for_task = pool.clone();
    tokio::spawn(async move {
        background_job(&pool_for_task).await;
    });

    run_server(pool).await?;
    Ok(())
}

Configuring PoolOptions

PoolOptions exposes a builder API for tuning pool behavior. The default configuration is suitable for development and light workloads; production applications will want to adjust at least max_connections.
The maximum number of connections the pool will hold open at any time. Defaults to 10.
PgPoolOptions::new()
    .max_connections(50)
    .connect(url).await?
Be mindful of your database server’s connection limit and the limits required by other applications connecting to the same server.
The minimum number of connections the pool tries to keep open at all times. Defaults to 0 (connections are opened on demand).Setting this to a nonzero value pre-warms the pool at startup so that the first requests do not incur connection-open latency:
PgPoolOptions::new()
    .min_connections(5)
    .max_connections(50)
    .connect(url).await?
The maximum time to wait for a connection from the pool before returning Error::PoolTimedOut. Defaults to 30 seconds.
use std::time::Duration;

PgPoolOptions::new()
    .acquire_timeout(Duration::from_secs(5))
    .connect(url).await?
Connections that have been idle for longer than this duration are closed and removed from the pool. Defaults to 10 minutes. Set to None to disable.
use std::time::Duration;

PgPoolOptions::new()
    .idle_timeout(Duration::from_secs(60 * 5))  // 5 minutes
    .connect(url).await?
The maximum age of any individual connection. Connections older than this are closed and replaced. Defaults to 30 minutes. Set to None to allow unlimited lifetimes.Retiring connections periodically lets the database server clean up session-level resources (parse trees, query caches, thread-local storage):
use std::time::Duration;

PgPoolOptions::new()
    .max_lifetime(Duration::from_secs(60 * 30))
    .connect(url).await?
When true (the default), each idle connection is verified with a ping before being returned to a caller. Disable if you are managing liveness checks yourself with before_acquire:
PgPoolOptions::new()
    .test_before_acquire(false)
    .connect(url).await?

Lifecycle hooks

PoolOptions provides three async callback hooks for customizing connection behavior.

after_connect

Called once when a new connection is first opened. Use it to set session-level parameters that are not available through ConnectOptions:
use sqlx::Executor;
use sqlx::postgres::PgPoolOptions;

let pool = PgPoolOptions::new()
    .after_connect(|conn, _meta| Box::pin(async move {
        conn.execute(
            "SET application_name = 'my_app'; SET search_path = 'my_schema';"
        ).await?;
        Ok(())
    }))
    .connect("postgres://localhost/mydb").await?;

before_acquire

Called before an idle connection is returned from the pool. Return Ok(true) to accept the connection or Ok(false) to discard it and try another. Useful for custom liveness checks:
use sqlx::{Connection, Executor};
use sqlx::postgres::PgPoolOptions;
use std::time::Duration;

let pool = PgPoolOptions::new()
    .test_before_acquire(false)
    .before_acquire(|conn, meta| Box::pin(async move {
        // Only ping connections that have been idle for more than 60 seconds
        if meta.idle_for > Duration::from_secs(60) {
            conn.ping().await?;
        }
        Ok(true)
    }))
    .connect("postgres://localhost/mydb").await?;

after_release

Called when a connection is returned to the pool. Return Ok(true) to put it back in the idle queue or Ok(false) to close it. Useful for releasing memory-heavy connections:
use sqlx::postgres::PgPoolOptions;
use std::time::Duration;

let pool = PgPoolOptions::new()
    .max_lifetime(None)
    .after_release(|conn, meta| Box::pin(async move {
        // Close connections that have been open for more than 6 hours
        if meta.age > Duration::from_secs(6 * 60 * 60) {
            return Ok(false);
        }
        Ok(true)
    }))
    .connect("postgres://localhost/mydb").await?;

Graceful shutdown

When your application exits, call pool.close().await to gracefully close all connections. Without this, the server side may not learn that connections are gone until a TCP keepalive timeout fires:
pool.close().await;
In tests, prefer #[sqlx::test] which handles pool lifetime automatically.

AnyPool for runtime database selection

AnyPool uses the URL scheme to select a driver at runtime, letting you write database-agnostic code or switch drivers via configuration:
use sqlx::any::AnyPool;

// Driver is chosen from the URL scheme: postgres://, mysql://, sqlite:
let pool = AnyPool::connect(&std::env::var("DATABASE_URL")?).await?;

let row = sqlx::query("SELECT 1").fetch_one(&pool).await?;
You must call sqlx::any::install_default_drivers() before using AnyPool to register the available drivers.

Build docs developers (and LLMs) love