Skip to content

第12章 Connection trait:acquire / close / ping 的生命周期

"A connection is not a handle—it's a correspondence. Opening it is polite, closing it is courteous, and dropping it is rude." —— 数据库连接礼仪的提纯

本章要点

  • Connection trait(sqlx-core/src/connection.rs:14)定义一条数据库连接的完整生命周期协议——共 14 个方法(7 必实现 + 7 默认):close / close_hard / ping / begin / shrink_buffers / flush / should_flush 必实现;begin_with / is_in_transaction / transaction / cached_statements_size / clear_cached_statements / connect / connect_with 带默认实现。涵盖建立、探活、事务、缓存管理、关闭五类职责。
  • close(self) vs close_hard(self)——前者发 graceful shutdown 消息(Postgres 的 Terminate、MySQL 的 COM_QUIT),后者只发 TCP FIN。Drop 默认走 close_hard 等价路径——因为 Drop 不能 async、不能发消息。
  • ping 探活——发协议级的轻量请求、等回应。Postgres 发一个空 simple query、MySQL 发 COM_PING、SQLite 用 sqlite3_stmt_busy 查询内部状态。
  • begin 开事务—— 默认走 ANSI BEGINbegin_with(statement) 允许自定义(比如 BEGIN ISOLATION LEVEL SERIALIZABLE)。返回 Transaction<'_, DB> 一个 RAII guard(第 15 章详细)。
  • transaction(closure) 便捷方法——给一个闭包、内部自动 begin / callback / commit-or-rollback。用户无需手动 begin/commit 两个方法。
  • ConnectOptions trait——url.parse() 解析连接字符串、connect() 真正建立连接。driver 特定的所有配置(SSL mode、application name、statement cache 容量)都通过这个 trait 的具体实现(PgConnectOptions 等)配置。
  • PgConnection 内部是 Box<PgConnectionInner>sqlx-postgres/src/connection/mod.rs:36-76)——box 化让 PgConnection 本身 stack size 稳定;inner 有 stream、statement cache(LRU)、type cache、transaction_status、log_settings 等十个字段。
  • SqliteConnection 是一个 worker 线程的 proxy——SQLite 的 C API 是同步的,sqlx 用一个专用 OS 线程承载 sqlite3 handle,主线程通过 channel 发命令到 worker。这是三家驱动里唯一用到 OS 线程的。

12.1 问题引入:连接不只是 TCP socket

一次数据库连接的生命周期远比"打开 socket、关闭 socket"复杂:

rust
let mut conn = PgConnection::connect("postgres://...").await?;   // 建立
conn.ping().await?;                                               // 探活
let mut tx = conn.begin().await?;                                 // 开事务
sqlx::query("...").execute(&mut *tx).await?;                      // 跑 SQL
tx.commit().await?;                                               // 提交
conn.close().await?;                                              // 关闭

六个阶段——每一个都对应具体的协议级交互:

  • connect:TCP 连接 + TLS 握手 + 认证握手 + 参数协商。Postgres 的 StartupMessage / SASL / AuthenticationOk 协议;MySQL 的 Handshake / HandshakeResponse / OK_Packet;SQLite 的 sqlite3_open_v2(走 C FFI)。
  • ping:轻量探活——发最小请求等响应、时间完全由网络 RTT 决定。用在连接池的"取连接前校验"。
  • begin:协议级开事务。三家驱动统一调用 sqlx-core/src/transaction.rs:277begin_ansi_transaction_sql(0)——返回字面量 BEGIN——一条 SQL 兼容三家。嵌套层(depth>0)转 SAVEPOINT _sqlx_savepoint_<N>。用户可以通过 begin_with(statement) 传自定义(例如 BEGIN ISOLATION LEVEL SERIALIZABLE 或 SQLite 的 BEGIN IMMEDIATE)。
  • execute(属于 Executor trait):发 SQL,后续协议按驱动不同。
  • commit / rollback(在 Transaction 上):发 COMMIT / ROLLBACK
  • close:告诉服务端"我要走了"——让服务端立即清理 session state 而不是等 TCP keepalive 超时。

如果跳过 close 直接 drop 会怎样?

  • Rust 端:TCP socket 被 drop、发 FIN 包。
  • Postgres 端:收到 FIN 后知道客户端断开。但取决于当前 session 状态——如果 session 里还有打开的事务、临时表,这些资源得等 session timeout 清理(通常几分钟)。
  • 连接限制场景:短时间内创建/丢弃大量连接会很快填满 server 的 max_connections,因为"senescent" 连接还在占用限制。

Connection trait 的每个方法都是在管理这个协议级的生命周期——让用户代码能礼貌地和数据库服务端交互。本章拆开每个方法的具体协议行为。

12.2 Connection trait 的 14 个方法

sqlx-core/src/connection.rs:14 的 trait 定义简化后:

rust
pub trait Connection: Send {
    type Database: Database<Connection = Self>;
    type Options: ConnectOptions<Connection = Self>;

    fn close(self) -> BoxFuture<'static, Result<(), Error>>;
    fn close_hard(self) -> BoxFuture<'static, Result<(), Error>>;
    fn ping(&mut self) -> BoxFuture<'_, Result<(), Error>>;

    fn begin(&mut self) -> BoxFuture<'_, Result<Transaction<'_, Self::Database>, Error>>
        where Self: Sized;
    fn begin_with(&mut self, statement: impl Into<Cow<'static, str>>)
        -> BoxFuture<'_, Result<Transaction<'_, Self::Database>, Error>>
        where Self: Sized;

    fn is_in_transaction(&self) -> bool;

    fn transaction<F, R, E>(&mut self, callback: F) -> BoxFuture<'_, Result<R, E>>
        where ...;

    fn cached_statements_size(&self) -> usize
        where Self::Database: HasStatementCache;
    fn clear_cached_statements(&mut self) -> BoxFuture<'_, Result<(), Error>>
        where Self::Database: HasStatementCache;

    fn shrink_buffers(&mut self);
    fn flush(&mut self) -> BoxFuture<'_, Result<(), Error>>;
    fn should_flush(&self) -> bool;

    fn connect(url: &str) -> BoxFuture<'static, Result<Self, Error>>
        where Self: Sized;
    fn connect_with(options: &Self::Options) -> BoxFuture<'_, Result<Self, Error>>
        where Self: Sized;
}

两个关联类型 DatabaseOptions——通过这两者把 Connection 和 Database trait 家族、ConnectOptions trait 家族关联起来。

14 个方法按功能分五组:

  • 生命周期:close / close_hard / flush / should_flush
  • 探活:ping
  • 事务:begin / begin_with / is_in_transaction / transaction
  • 缓存管理:cached_statements_size / clear_cached_statements / shrink_buffers
  • 建立:connect / connect_with

每组我们逐个看。

12.3 close vs close_hard:两种关闭仪式

connection.rs:22-41 的两个方法:

rust
/// Explicitly close this database connection.
fn close(self) -> BoxFuture<'static, Result<(), Error>>;

/// Immediately close the connection without sending a graceful shutdown.
#[doc(hidden)]
fn close_hard(self) -> BoxFuture<'static, Result<(), Error>>;

close(self) 做的事:

  1. 发服务端特定的 "我要走了" 消息:
    • Postgres:Terminate 消息(F 类型字节、\0 载荷)。
    • MySQL:COM_QUIT
    • SQLite:sqlite3_close()(C API)。
  2. 等待服务端处理(Postgres 立即关 socket、MySQL 发 ACK)。
  3. 关闭 TCP(Rust 端)。

close_hard(self) 做的事:

  1. 不发协议消息。
  2. 直接关闭 TCP(让服务端靠 FIN 发现客户端走了)。

为什么需要两个? 源码 close 的文档注释说得很清楚(connection.rs:25-34):

While connections can simply be dropped to clean up local resources, the Drop handler itself cannot notify the server that the connection is being closed because that may require I/O to send a termination message.

Drop 不能做 async I/O——发 Terminate 消息是异步操作,drop 是同步函数。所以 sqlx 提供两条路径:

  • close(self).await —— 用户显式关、正确发消息。
  • drop(conn) —— 等价 close_hard、只发 TCP FIN。

close_hard#[doc(hidden)]——不推荐用户直接调,而是用户调 close 或让 drop 自动走等价路径。

12.3.1 Drop 实现里如何处理连接关闭

PgConnection 的 Drop(简化):

rust
impl Drop for PgConnection {
    fn drop(&mut self) {
        // 同步发 Terminate 消息到 stream 的 write buffer
        let _ = self.inner.stream.write_msg(Terminate);
        // 不等 flush——Tokio runtime 可能已经 shutdown
    }
}

注意 write_msg 只写到 buffer、不保证发送。如果 Drop 发生在正常 runtime 还活着的时候,buffer 通常能被刷出;如果 runtime 已经 shutdown(main 结束),buffer 里的字节可能没发出——等 TCP 关闭时 OS 层决定是否发。

这条最佳 effort Drop 是 Rust async 生态的现实——第 1 章 §1.6 也讨论过 Transaction::drop 的类似局限。用户想要保证协议消息发出,必须显式 close().await

12.4 ping:探活

connection.rs:44

rust
fn ping(&mut self) -> BoxFuture<'_, Result<(), Error>>;

发轻量请求验证连接还活着。每家实现不同:

  • Postgres:发一个 empty simple query (Q 消息带 \0)——服务端返回 EmptyQueryResponse + ReadyForQuery。来回约 1-5ms(本地网络)。
  • MySQL:发 COM_PING (0x0e)——服务端返回 OK_Packet。也是 1-5ms。
  • SQLite:检查 sqlite3_stmt_busy + 内部状态——不走网络,几微秒。

ping 的典型用户是 Pool(第 14 章 §14.3)——acquire 拿到的连接如果闲置了一段时间,先 ping 验证还能用、再交给用户。这避免"用户拿到一个已经被服务端超时断开的连接"——ping 早一秒发现、失败的话 Pool 丢弃这个连接、建新的。

ping 的代价:每次 acquire 都多 1-5ms。sqlx PoolOptions::test_before_acquire 默认 truesqlx-core/src/pool/options.rs:149)—— 这条是"拿到坏连接再失败"和"acquire 路径多一跳"的权衡、sqlx 选后者保证正确性优先。延迟极敏感的业务可以 .test_before_acquire(false) 关掉、配合 before_acquire 回调做更轻量的检查、或者干脆让坏连接失败触发 Pool 自愈。

12.5 begin / begin_with / transaction

connection.rs:49-65 的事务三方法:

rust
fn begin(&mut self) -> BoxFuture<'_, Result<Transaction<'_, Self::Database>, Error>>;

fn begin_with(&mut self, statement: impl Into<Cow<'static, str>>)
    -> BoxFuture<'_, Result<Transaction<'_, Self::Database>, Error>>
{
    Transaction::begin(self, Some(statement.into()))
}

fn transaction<F, R, E>(&mut self, callback: F) -> BoxFuture<'_, Result<R, E>>
    where F: FnOnce(&mut Transaction<'_, Self::Database>) -> BoxFuture<'_, Result<R, E>>,
          R: Send, E: From<Error> + Send,
{
    Box::pin(async move {
        let mut transaction = self.begin().await?;
        let ret = callback(&mut transaction).await;
        match ret {
            Ok(ret) => { transaction.commit().await?; Ok(ret) }
            Err(err) => { transaction.rollback().await?; Err(err) }
        }
    })
}

begin() 默认用 ANSI SQL 的 BEGIN(第一层事务)或 SAVEPOINT _sqlx_savepoint_N(嵌套)——发到服务端、等 OK、返回 Transaction。

begin_with(stmt) 允许自定义 BEGIN 语句——典型用例:

rust
let mut tx = conn.begin_with("BEGIN ISOLATION LEVEL SERIALIZABLE READ WRITE").await?;

覆盖默认的 BEGIN——Postgres 下可以指定 isolation level。MySQL 下同样支持。

transaction(closure)便捷包装器——闭包返回 Ok 就 commit、Err 就 rollback。用户代码省掉手动 commit/rollback 两行:

rust
// 用 transaction 包装
let result = conn.transaction(|tx| Box::pin(async move {
    sqlx::query("UPDATE ...").execute(&mut **tx).await?;
    sqlx::query("INSERT ...").execute(&mut **tx).await?;
    Ok::<_, sqlx::Error>(())
})).await?;

// 等价的手写版
let mut tx = conn.begin().await?;
sqlx::query("UPDATE ...").execute(&mut *tx).await?;
sqlx::query("INSERT ...").execute(&mut *tx).await?;
tx.commit().await?;
// drop(tx)  // 如果 commit 没调 drop 会 rollback

transaction 的价值在错误路径——手写版本里 ? 冒泡时如果忘了 rollback 会依赖 Drop(Drop 的"尽力 rollback"在第 15 章详细);transaction 版本里错误路径显式 rollback。第 15 章会详细对比两者的事务保证差异。

12.6 cached_statements_size / clear_cached_statements

connection.rs:80-99

rust
fn cached_statements_size(&self) -> usize
    where Self::Database: HasStatementCache,
{ 0 }

fn clear_cached_statements(&mut self) -> BoxFuture<'_, Result<(), Error>>
    where Self::Database: HasStatementCache,
{ Box::pin(async move { Ok(()) }) }

两个方法受 HasStatementCache 约束——只有 Postgres 和 MySQL 的 Connection 能调(第 3 章 §3.9)。SQLite 没实现 HasStatementCache,这两方法对 SqliteConnection 编译期不存在。

cached_statements_size 返回 LRU 缓存里当前有几条 prepared statement。典型用法是监控:

rust
tracing::debug!("statements cached: {}", conn.cached_statements_size());

clear_cached_statements 主动清空缓存——发 Close 消息给服务端释放每个 prepared statement、本地 LRU 也清。什么时候用?

  1. long-running 连接内存占用——一个连接跑一天、缓存上千条 statement——手动 clear 回收。
  2. schema 变更后——DDL 改了列类型,旧的 prepared statement 基于旧 schema、继续用会"列类型不匹配"。clear 强制下次重新 prepare。
  3. 测试场景——测试之间清空缓存隔离状态。

大多数生产代码不调——让 LRU 自动驱逐(默认容量 100,MRU 替换)。

12.7 shrink_buffers

connection.rs:101-119

rust
/// Restore any buffers in the connection to their default capacity, if possible.
fn shrink_buffers(&mut self);

释放连接内部的扩展 buffer——用过大查询后 buffer 可能扩到几 MB,shrink_buffers 把 capacity 缩回默认(通常 8KB)。典型场景:

rust
// 跑一个大查询拉回 10 MB 结果
sqlx::query("SELECT huge_blob FROM ...").fetch_all(&mut conn).await?;
// buffer 现在 10MB 大小
conn.shrink_buffers();  // 缩回默认
// buffer 现在 ~8KB

为什么不自动?因为 shrink 需要重新分配 Vec——如果下一条 query 又要大 buffer,就浪费了一次 alloc + copy。sqlx 让用户显式声明"这条大查询之后我不再需要大 buffer"——常见于连接归还 Pool 前。

12.8 connectconnect_with

connection.rs:166-183

rust
fn connect(url: &str) -> BoxFuture<'static, Result<Self, Error>>
    where Self: Sized,
{
    let options = url.parse();
    Box::pin(async move { Self::connect_with(&options?).await })
}

fn connect_with(options: &Self::Options) -> BoxFuture<'_, Result<Self, Error>>
    where Self: Sized,
{
    options.connect()
}

两条路径:

  • connect(url_string) —— 解析 URL 成 Options、再 connect_with。适合"URL 里所有配置都够用"的简单场景。
  • connect_with(options) —— 已经手动构造好 Options(带 log_statements、statement_cache_capacity 等高级配置)——直接连。

URL 解析走 Options: FromStr——由 ConnectOptions trait 保证(见下节)。解析失败(URL 格式错、scheme 不对)返回错误。

典型用法

rust
// URL 简单配置
let conn = PgConnection::connect("postgres://user:pass@host/db").await?;

// Options 详细配置
let opts = PgConnectOptions::new()
    .host("host")
    .database("db")
    .username("user")
    .statement_cache_capacity(500)  // 默认 100
    .log_statements(LevelFilter::Info);
let conn = PgConnection::connect_with(&opts).await?;

12.9 ConnectOptions trait

connection.rs:222-270 的 ConnectOptions:

rust
pub trait ConnectOptions: 'static + Send + Sync + FromStr<Err = Error> + Debug + Clone {
    type Connection: Connection<Options = Self> + ?Sized;

    fn from_url(url: &Url) -> Result<Self, Error>;
    fn to_url_lossy(&self) -> Url { unimplemented!() }
    fn connect(&self) -> BoxFuture<'_, Result<Self::Connection, Error>>;
    fn log_statements(self, level: LevelFilter) -> Self;
    fn log_slow_statements(self, level: LevelFilter, duration: Duration) -> Self;
    fn disable_statement_logging(self) -> Self { ... }
}

super-trait bound 很重——'static + Send + Sync + FromStr + Debug + Clone

  • FromStr:URL 字符串解析。
  • Debug + Clone:日志友好、支持多次连接(从同一 Options 建多个连接)。
  • 'static + Send + Sync:能跨线程传(Pool 需要)。

from_urlurl::Url 解析成 Options。包括解析 scheme、host、port、user:password、database、query string 参数。Postgres 的 PgConnectOptions::from_urlsqlx-postgres/src/options/parse.rs)识别 postgres://postgresql:// 开头、从 query string 读 ?sslmode=require 等参数。

to_url_lossy 反向——把 Options 序列化回 URL 字符串。标记 lossy 因为某些内存配置(如 statement_cache_capacity)没有 URL 表示——序列化时丢失。用于 logging / debugging。

connect 是真正建立连接的方法——driver 特定的协议握手全在这里。

log_statements / log_slow_statements链式配置——返回 Self 支持 .log_statements(Info).log_slow_statements(Warn, Duration::from_secs(1))

12.9.1 PgConnectOptions 的配置示例

rust
let opts = PgConnectOptions::new()
    .host("localhost")
    .port(5432)
    .database("myapp")
    .username("user")
    .password("pass")
    .ssl_mode(PgSslMode::Require)
    .application_name("my_service")
    .statement_cache_capacity(500)
    .log_statements(LevelFilter::Debug)
    .log_slow_statements(LevelFilter::Warn, Duration::from_millis(100));

PgConnectOptions 有40+ 个配置方法——covers 从 SSL 到 application_name、options 参数、TCP keepalive 的每一项。完整列表在 sqlx-postgres/src/options/mod.rs

MySQL 的 MySqlConnectOptions、SQLite 的 SqliteConnectOptions 各自有 20+ 和 15+ 个配置方法——每家的协议特性决定配置项集合。

12.10 LogSettings:日志配置

connection.rs:194-217

rust
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct LogSettings {
    pub statements_level: LevelFilter,
    pub slow_statements_level: LevelFilter,
    pub slow_statements_duration: Duration,
}

impl Default for LogSettings {
    fn default() -> Self {
        LogSettings {
            statements_level: LevelFilter::Debug,
            slow_statements_level: LevelFilter::Warn,
            slow_statements_duration: Duration::from_secs(1),
        }
    }
}

三字段

  • statements_level—— 所有 SQL 语句的日志级别(默认 Debug)。
  • slow_statements_level—— 慢查询(超过 duration)的日志级别(默认 Warn)。
  • slow_statements_duration—— 慢查询阈值(默认 1 秒)。

这些设置通过 ConnectOptions 的 log_statements / log_slow_statements 方法配置。每条 SQL 执行后 sqlx 的 QueryLogger(第 21 章)会检查时长、决定用哪个 level 记录。

实际效果

rust
// 默认 —— 每条 SQL 走 log::debug!,慢查询(>1s)升级成 log::warn!
// 用 tracing_subscriber 设置 log level 过滤

开发环境通常设 statements = Info 看每条 SQL;生产环境设 statements = Off、slow_statements = Warn 只记慢查询。这是 ConnectOptions::log_statementslog_slow_statements 两个方法的典型搭配。

12.11 PgConnection 内部结构

sqlx-postgres/src/connection/mod.rs:36-76

rust
pub struct PgConnection {
    pub(crate) inner: Box<PgConnectionInner>,
}

pub struct PgConnectionInner {
    pub(crate) stream: PgStream,
    process_id: u32,
    secret_key: u32,
    next_statement_id: StatementId,
    cache_statement: StatementCache<(StatementId, Arc<PgStatementMetadata>)>,
    cache_type_info: HashMap<Oid, PgTypeInfo>,
    cache_type_oid: HashMap<UStr, Oid>,
    cache_elem_type_to_array: HashMap<Oid, Oid>,
    pub(crate) pending_ready_for_query_count: usize,
    transaction_status: TransactionStatus,
    pub(crate) transaction_depth: usize,
    log_settings: LogSettings,
}

PgConnection 外层是 Box<PgConnectionInner> —— Box 化的原因是让 PgConnection 本身stack size 稳定(指针大小,8 字节)。如果直接内嵌 Inner(几百字节),move PgConnection 时 memcpy 开销大——Box 化省这份开销。

十个字段的分工:

  • stream: PgStream —— TCP 或 Unix socket 的缓冲 I/O stream,带 TLS 和序列化器。
  • process_id + secret_key —— Postgres startup 时服务端发的 BackendKeyData——用于发送 cancel 请求。
  • next_statement_id —— 每次 prepare 分配的递增 ID("s_0""s_1"、...)。
  • cache_statement —— prepared statement 的 LRU 缓存(§12.12)。
  • cache_type_info / cache_type_oid / cache_elem_type_to_array —— 三张类型元数据 HashMap,用户自定义类型的 OID 查询缓存(第 6 章 §6.4.5 讨论过)。
  • pending_ready_for_query_count —— 未处理的 ReadyForQuery 消息计数——Postgres 协议的流水线跟踪。
  • transaction_status + transaction_depth —— 事务状态机(第 15 章详细)。
  • log_settings —— 前面讨论的日志配置。

这十个字段合起来描述一条 Postgres 连接的所有 in-memory state。每个字段对应具体协议需求——没有一个是"偶尔用用"的。

12.12 StatementCache:LRU 预处理缓存

sqlx-core/src/common/statement_cache.rs:5-8

rust
pub struct StatementCache<T> {
    inner: LruCache<String, T>,
}

基于 hashlink::LruCache 的 LRU——key 是 SQL 字符串、value 是驱动特定的 statement 句柄(Postgres 下是 (StatementId, Arc<PgStatementMetadata>))。

insert 方法statement_cache.rs:26-38):

rust
pub fn insert(&mut self, k: &str, v: T) -> Option<T> {
    let mut lru_item = None;
    if self.capacity() == self.len() && !self.contains_key(k) {
        lru_item = self.remove_lru();  // 满了、踢最老
    } else if self.contains_key(k) {
        lru_item = self.inner.remove(k);  // 同 key 替换
    }
    self.inner.insert(k.into(), v);
    lru_item
}

两种淘汰情况

  1. 缓存满且 key 新——踢最 least recently used 的那条。
  2. key 已存在——替换旧值、返回旧值(调用方会 close 掉对应的 server-side statement)。

容量默认 100(PgConnectOptions::new() 的默认)。用户可以调:

rust
let opts = PgConnectOptions::new().statement_cache_capacity(500);

容量选择的权衡

  • 小容量(10-50) —— memory footprint 小、但频繁 miss 导致 re-prepare 开销。
  • 中等容量(100-500) —— 大多数业务的 sweet spot。
  • 大容量(1000+) —— 长连接 + 高 SQL 基数场景有用,但 server 侧每个 statement 占资源。

12.13 SqliteConnection:worker 线程模型

SQLite 的 Connection 是三家里最独特的。SQLite 的 C API(sqlite3_stepsqlite3_bind_*)全部同步——调用时阻塞直到完成。sqlx 要把它包装成 async,只能让一个专用 OS 线程承载 sqlite3 handle、主线程通过 channel 发命令过去

sqlx-sqlite/src/connection/mod.rs:58+(简化):

rust
pub struct SqliteConnection {
    pub(crate) worker: ConnectionWorker,  // 到 worker 线程的通道
    pub(crate) row_channel_size: usize,
    // ...
}

ConnectionWorker 内部持有:

  • 一个 std::thread::JoinHandle —— worker 线程。
  • flume::Sender<Command> —— 主线程发命令过去。
  • flume::Receiver<Response> —— worker 发结果回来。

工作流程

每次 async 调用都是跨线程 channel 通信——命令过去、结果回来。Worker 线程专心做 C API 调用、主线程的 async task 正常跑其他 Future。

代价

  1. 每次 query 多一次 channel ping-pong——约 1-10μs 延迟。
  2. 一个 OS 线程 per connection——100 个连接池占 100 个线程。

收益

  1. SQLite 的同步 API 和 async 世界解耦——用户代码里 SQLite 看起来和 Postgres/MySQL 一样异步。
  2. worker 线程内部可以自由调 C API——不用担心阻塞 Tokio runtime。

替代方案("真 async SQLite")需要修改 libsqlite3 本身——工作量巨大。sqlx 选了务实路径——工作线程包装

这条设计也解释了为什么 SqliteConnection 需要手动 unsafe impl Send + Sync(第 7 章 §7.8.1 讨论过 SqliteRow 的类似问题)——内部有 *mut sqlite3,Rust 类型系统需要手动保证它在只有 worker 线程访问时安全。

12.14 三家 Connection 对照

总结三家的结构差异:

维度PgConnectionMySqlConnectionSqliteConnection
核心 I/OPgStream(TCP/UDS + TLS)MySqlStreamConnectionWorker(OS 线程 + channel)
状态字段Inner 十字段(transaction / cache 等)类似结构Worker 代理
statement cacheLRU(默认 100)LRU(默认 100)有(§12.13 worker 内部)
类型缓存三张 HashMap(OID 等)较少(MySQL 类型系统简单)
close 消息TerminateCOM_QUITsqlite3_close() C 调用
ping 消息empty simple queryCOM_PINGsqlite3_stmt_busy 内部状态
代码量(connection/)~1500 行(不含 describe.rs)~1300 行~2400 行(不含 explain.rs;含 worker)

Postgres Connection 最复杂——协议 + 类型缓存两层叠加;SQLite Connection 最特殊——worker 线程模型是独一份。MySQL 是中间——协议复杂但类型缓存相对简单。

12.14a 连接建立的协议握手详解

connect_with 表面上一行调用,底下是一套复杂的协议握手。以 Postgres 为例:

10 条协议消息——本地连接 1-5ms,跨机房 50-200ms。这就是为什么连接池如此重要——避免每次 query 都重跑这套握手。

认证机制:Postgres 11+ 默认用 SCRAM-SHA-256(最安全);老版本可能用 MD5、trust、cert。每种机制对应不同的消息序列——sqlx 的 sqlx-postgres/src/connection/sasl.rs 实现 SCRAM 的所有细节(HMAC、salted password、proof exchange)。

MySQL 握手结构类似但协议细节不同——CapabilitiesFlags 协商、Handshake V10、HandshakeResponse41、OK_Packet。sqlx-mysql/src/connection/establish.rs 实现全套。

SQLite 没有网络握手——sqlite3_open_v2 直接打开文件(或 :memory:)、初始化内部结构、准备好。但它有工作线程启动——std::thread::spawn 的开销在几十微秒级(比 Postgres TCP 握手快 100 倍)。

12.15 连接池和单连接的对比

用户代码里有两种获取连接的路径——直接 Connection::connect 和通过 Pool

rust
// 路径 A:直接单连接
let mut conn = PgConnection::connect("postgres://...").await?;
sqlx::query("...").execute(&mut conn).await?;
conn.close().await?;

// 路径 B:通过 Pool
let pool = PgPool::connect("postgres://...").await?;
sqlx::query("...").execute(&pool).await?;  // Pool 自动借连接 + 归还

什么时候用 A(直接 Connection)?

  • 单次性脚本(migration、一次性维护任务)——不需要池化。
  • CLI 工具——跑一会儿就退出,一个连接够了。
  • 专用用途(Listener、Copy FROM/TO)——需要独占连接,不能让 Pool 自动回收。

什么时候用 B(Pool)?

  • 长时间跑的服务(web server、worker)——Pool 复用连接。
  • 高并发请求——Pool 管理并发 limit。
  • 连接要共享(多个 async task 用同一服务)——Pool 是 Clone + Arc 友好。

本章讲的 Connection 是 A 场景的主角——直接 connect、调方法、close。第 13-14 章讲的 Pool 是 B 场景——内部管理一堆 Connection、给用户抽象成"取一条来用"。

两者在 Executor trait 层统一——无论 A 还是 B,最终 fetch / execute 方法都是对 Connection 的操作。Pool 的 fetch 内部 pool.acquire() 拿到一条 Connection、调 &mut conn.fetch_many——本章 Connection trait 的 Executor 方法实现就是为了支持这条路径(见第 4 章 §4.4.2)。

12.15.1 连接断开与重连

生产环境里连接会因各种原因断开:

  • 数据库重启——server 重启、所有连接 reset。
  • 网络抖动——TCP 连接超时或 RST。
  • 防火墙超时——idle 连接被中间防火墙断。
  • 云数据库 maintenance——AWS RDS 的定期维护窗口。

sqlx 对断开的处理:

单 Connection 的处理

  • 下次调 fetch / execute 时,底层 stream read/write 失败——返回 Error::Io
  • 用户代码通常把 Result<_> ? 冒泡——调用方自己决定重建连接。

Pool 的处理(第 14 章):

  • Pool 发现 connection broken(error 反馈或 ping 失败)——从池里删除、建新的。
  • 用户代码的视角只是"第一次慢几百毫秒"——背后 Pool 自动恢复。

没有"单 Connection 自动重连"——sqlx 故意不提供。因为自动重连要处理事务中断(第 15 章讨论)——如果事务中途连接断了,sqlx 不能透明重建(数据一致性会出问题)。只有 Pool 级别的重建安全(新连接 = 新事务边界)。

12.16 Connection trait 的演进

Connection trait 从 0.3 到 0.8 的演进:

  • 0.3:Connection trait 只有 close / ping / execute / fetch_all——没有 begin / transaction。
  • 0.4:加 begin 方法;transaction 便捷方法首次引入。
  • 0.5:shrink_buffers 加入;HasStatementCache-guarded cached_statements_size 合入。
  • 0.6:close_hard 和 close 分离(之前只有一个)——让 Drop 路径和显式 close 分开。
  • 0.7:GAT 改造;begin_with 加入(支持自定义 BEGIN 语句)。
  • 0.8(本书版本):is_in_transaction 加入;transaction 闭包的 trait bound 精化。

每次加方法都是从生产反馈里长出来的需求——close_hard 是因为开发者发现 Drop 路径的协议消息不发送;shrink_buffers 是因为长连接的内存问题;begin_with 是因为用户想用 SERIALIZABLE 隔离级别。

这条演进也显示 Connection trait 是有机生长的——没有一次性设计、而是每个版本加一两个 method。这让 sqlx 的 Connection API 始终贴近实际需要——没有过度抽象、没有"预留未来"的废方法。

12.16.1 常见 Connection 使用陷阱

生产代码里 Connection 相关的常见错误:

陷阱 1:忘 close 导致 server 资源泄漏

rust
async fn do_work() {
    let mut conn = PgConnection::connect(...).await.unwrap();
    sqlx::query(...).execute(&mut conn).await.unwrap();
    // 函数结束 conn drop —— 只发 TCP FIN 不发 Terminate
}

一次性还好;高频调用(每秒上百次)会让 Postgres server 的 idle connections 短时间累积——撞到 max_connections 或让 monitoring 告警误报。正确conn.close().await? 显式关。

陷阱 2:handler 里直接 connect 而不用 Pool

rust
async fn handler(database_url: String) -> Result<Json<User>> {
    let mut conn = PgConnection::connect(&database_url).await?;  // 每请求都握手
    let user = sqlx::query_as!(User, "...").fetch_one(&mut conn).await?;
    Ok(Json(user))
}

每个 HTTP 请求都做完整协议握手(3-5ms 本地 / 50-200ms 跨机房)——handler 延迟直接加上这个。正确:handler 收 State(pool: PgPool)——Pool 内部复用连接。

陷阱 3:跨 await 持有 &mut Connection

rust
let mut conn = pool.acquire().await?;
let user_stream = sqlx::query(...).fetch(&mut conn);
some_other_long_op().await;  // conn 被这个 future 独占
let user = user_stream.try_next().await?;  // 期间 conn 被占着

fetch 返回 stream 借用了 conn——别的 async 操作期间连接被占着不能给其他任务用。高并发场景 Pool 的连接数 = 并发占用数。正确:先 fetch 完再做其他 await;或者 fetch_all 收到 Vec 再释放 conn。

陷阱 4:transaction 里调 Self::close

rust
let mut tx = conn.begin().await?;
sqlx::query(...).execute(&mut *tx).await?;
conn.close().await?;  // 编译错 —— tx 借了 conn

Transaction 持有 &mut conn——close 需要 owned self——编译拒绝。但这条检查的是 tx 是否还活——如果 tx 已经 commit/rollback,borrow 释放、close 合法。

陷阱 5:同一 connection 并发执行多条 query

rust
let mut conn = pool.acquire().await?;
tokio::join!(
    sqlx::query(...).execute(&mut conn),  // 编译错
    sqlx::query(...).execute(&mut conn),
);

&mut conn 只能有一个借用——join! 编译失败。SQL 协议本身是单连接串行的——并发执行必须两条连接(两次 acquire)。正确:从 pool 各 acquire 一条,并发。

五个陷阱覆盖 Connection 使用的 80% 坑。记住它们能避免大多数生产环境的 "奇怪问题"。

12.17 本章小结

本章把 Connection trait 和三家实现的生命周期完整拆开:

  1. Connection trait 14 个方法(§12.2)—— 生命周期(close/close_hard/flush/should_flush)、探活(ping)、事务(begin/begin_with/is_in_transaction/transaction)、缓存管理(cached_statements_size/clear_cached_statements/shrink_buffers)、建立(connect/connect_with)。两个关联类型(Database 和 Options)把 trait 家族串起来。
  2. close vs close_hard(§12.3)—— 前者发 graceful 消息、后者只发 TCP FIN。Drop 走等价 close_hard 路径——因为 Drop 不能 async。
  3. ping 协议级探活(§12.4)—— Postgres empty query、MySQL COM_PING、SQLite 内部状态。
  4. transaction 便捷方法(§12.5)——闭包 + 自动 commit/rollback,比手写 begin / commit 更安全(错误路径显式)。
  5. cached_statements_size / clear(§12.6)—— 受 HasStatementCache 守护,SQLite 不可见。
  6. shrink_buffers 主动释放(§12.7)—— 避免连接内存占用持续膨胀。用户需显式调。
  7. connect / connect_with(§12.8)—— URL 解析 vs 详细 Options 配置两条路径。
  8. ConnectOptions trait(§12.9)—— FromStr / Clone / Debug / Send / Sync super-trait;from_url / connect / log_* 方法。
  9. LogSettings(§12.10)—— statements_level + slow_statements_level + slow_statements_duration。
  10. PgConnection 内部十字段(§12.11)—— Box<Inner> 让 stack size 稳定;字段包括 stream、statement cache、type cache、transaction state、log_settings。
  11. StatementCache LRU(§12.12)—— 默认容量 100,满了踢最老、同 key 替换返回旧值供 server-side close。
  12. SqliteConnection worker 线程(§12.13)—— 独一份的 OS 线程模型,把同步 C API 包装成 async;代价每 query 多 1-10μs channel 通信。

下一章我们进入 Pool 外部 API——PoolOptions / acquire / try_acquire / close / fetch_*——连接池如何管理连接的生命周期、分配 / 归还的协议。

12.17.1 Connection 方法的典型调用频率

把本章讨论的 Connection 方法按生产环境调用频率排序:

方法调用频率典型调用方
connect / connect_with每分钟几次到每秒几十次Pool 建立 + 补充连接
Executor::fetch_*(间接)每秒数十到数万次每条业务查询
begin / transaction每秒到每分钟事务边界(写操作)
ping每次 acquire(如启用)Pool 的 test_before_acquire
cached_statements_size偶尔监控 / debug
clear_cached_statements极少DDL 后 / 长连接内存压力
shrink_buffers按需大查询后手动调
close每分钟几次Pool 淘汰、应用关闭
close_hard从不(用户)Drop 内部走

一条观察:业务代码几乎 100% 时间在调 Executor 方法(fetch/execute),Connection 自身的方法是辅助管理——连接建立、事务开启、健康检查。这也是为什么 Connection trait 的14 个方法里用户直接调的只有 2-3 个(connect、begin、偶尔 close)——其他全是被 Pool / Executor 等高层间接调。

这条分布给你生产 profiling 的优先级

  • 性能问题优先查 fetch 路径(第 16-18 章的驱动实现)。
  • 连接数问题查 connect / close 节奏(第 13-14 章的 Pool)。
  • 内存问题查 statement cache / shrink_buffers(本章)。

按这个优先级定位问题比盲目 profile 所有 Connection 方法快得多。

12.18 Connection 设计的三条原则

本章讲的 Connection trait 可以萃取出三条可复用的 API 设计原则:

1. 显式 close 优于依赖 Drop。sqlx 的 close().await 鼓励用户显式关连接——而不是让 drop 悄悄做。这是"用方法表达副作用"的原则——任何有网络 I/O 的关闭动作都应该有显式方法、让用户意识到"这里有远程操作"。Drop 只做最小必要清理。

2. Trait bound 守护能力差异cached_statements_size / clear_cached_statements / persistent 都用 where DB: HasStatementCache 守护——SQLite 下编译期就不存在。这比"运行时返回 NotSupported 错误"优雅得多——把能力差异编码进类型系统。Rust 的 trait bound 让这件事低成本可行。

3. 提供便捷方法但不废除原始方法transaction(closure) 便捷 + begin 原始两者并存——用户按场景选。便捷方法降低常见路径的代码量、原始方法保持灵活性。不强迫所有人用便捷版——因为总有闭包无法表达的场景(比如跨 transaction 复杂控制流)。

这三条对 Rust 下设计"网络资源"trait 都适用。sqlx 的 Connection 是此类设计的良好范例——协议完整性、类型安全、灵活性三者兼得。

12.19 Connection 的总体定位

第四部分"连接与事务"的起点是 Connection trait——它是 sqlx 整个架构的底层资源抽象:

Application (handlers, business logic)

Query / QueryBuilder / query! (第 9-11 章)

Executor trait (第 4 章)

Pool ← 第 13-14 章

Transaction ← 第 15 章

Connection ← 本章

Driver-specific stream / worker

Database server

Connection 在这条栈里扮演"资源对等体"——上层(Executor / Pool / Transaction)都是在 Connection 之上构造的抽象。理解 Connection 的生命周期,你才能理解 Pool 的 Acquire/Release 为什么要小心、Transaction 的 Drop 为什么只能"尽力 rollback"——所有这些都绕不开 Connection 本身的协议约束

这也是为什么第四部分从 Connection 开始——把"最底的底"讲清楚,之后的 Pool / Transaction 讨论都建立在这个基础上。读完本章你对"一条 Postgres/MySQL/SQLite 连接到底是什么"有了具体答案——不只是 TCP socket,而是一整套协议状态 + 本地缓存 + 生命周期礼仪。

12.20 Connection 的三句话总结

最后用三句话概括本章:

1. Connection 是有协议状态的资源——不是一个无状态的 TCP socket、而是一条携带 transaction depth、prepared statement cache、type OID cache 的对话。每个方法都在推进或查询这个状态。

2. 生命周期礼仪比代码正确性更难——connect / close / ping / shrink_buffers 的正确调用时机是 sqlx 生产用户学的最久的东西。代码跑得通不代表连接管理得好——生产问题大多数在这一层。

3. 三家驱动的 Connection 共享抽象但差异大——Postgres 和 MySQL 是 TCP + 协议栈、SQLite 是 worker 线程 + C FFI。trait 让 API 统一、driver 让实现自由——这是 sqlx-core trait 家族设计的典型价值兑现。

读完第 12 章 Connection 后,下一站 Pool 是Connection 的管理者——怎么把多个 Connection 组织起来给用户复用。Connection 是"一个",Pool 是"一群"——前者的生命周期礼仪决定了后者的管理策略。

12.21 跨 DB 初始化的一致性对比

把三家 DB 的初始化代码放一起有助于直观理解它们的共性:

rust
// Postgres
use sqlx::postgres::{PgConnection, PgConnectOptions};
use sqlx::ConnectOptions;

let opts = PgConnectOptions::new()
    .host("localhost").port(5432).database("mydb")
    .username("user").password("pass")
    .ssl_mode(PgSslMode::Require);
let mut conn = opts.connect().await?;

// MySQL
use sqlx::mysql::{MySqlConnection, MySqlConnectOptions};

let opts = MySqlConnectOptions::new()
    .host("localhost").port(3306).database("mydb")
    .username("user").password("pass");
let mut conn = opts.connect().await?;

// SQLite
use sqlx::sqlite::{SqliteConnection, SqliteConnectOptions};

let opts = SqliteConnectOptions::new()
    .filename("mydb.sqlite")
    .create_if_missing(true)
    .journal_mode(SqliteJournalMode::Wal)
    .synchronous(SqliteSynchronous::Normal);
let mut conn = opts.connect().await?;

共同模式XxxConnectOptions::new() + 链式配置 + .connect().await?。用户一旦学会一家,其他两家看配置列表就能上手。

差异

  • Postgres:网络相关参数最多(SSL、application_name、statement_timeout 等)。
  • MySQL:类似 Postgres 但少 SSL 模式的精细度。
  • SQLite:文件相关(filename、create_if_missing)+ journal mode。没有网络配置。

这条对比显示 sqlx 的一致 API 跨 DB 能力落到 Connection 层——配置方法可能不同(因为 DB 差异是本质的)但调用路径和链式风格统一。用户代码可以通过泛型 fn do_work<DB: Database>(pool: &Pool<DB>) 写跨 DB 的业务逻辑,只在初始化阶段(本章主题)选具体驱动。

12.22 Connection 与更底层的 I/O 抽象

Connection 方法背后的 I/O 路径最终落到 Tokio / async-std 的 AsyncRead / AsyncWrite trait。以 PgConnection 的 PgStream 为例:

PgStream
  └── tokio_io::BufReader + BufWriter(缓冲)
      └── TLS(可选)
          └── tokio::net::TcpStream(或 UnixStream)
              └── OS socket 系统调用

每一层都是薄包装——PgStream 负责序列化 Postgres 消息、BufReader/BufWriter 负责批量 I/O、TLS 负责加密、TcpStream 负责 socket。

这套分层让 sqlx 的协议实现(sqlx-postgres/src/message/不用关心加密 / 缓冲 / socket 细节——只关心"按协议格式读写字节"。这种职责分离是 Rust async 生态能力的典型体现——tokio + rustls + sqlx 各司其职、组合出一条可靠的协议栈。

读懂这条分层,你在遇到连接相关的性能问题(比如"为什么我这条查询慢 20ms")时能知道去哪看:

  • TLS 握手慢?——查 rustls 或 PgConnectOptions::ssl_mode
  • 网络 RTT 慢?——查网络层(不是 sqlx 问题)。
  • 协议序列化慢?——sqlx-postgres 的 message 模块(第 16 章)。
  • statement cache 不命中?——调大 cache capacity。

知道抽象栈的每一层承担什么,比盲目怀疑任何一层都更高效。Connection 是这条栈在 sqlx 里的顶层入口——本章讲完了它的全貌、为后续几章讨论 Pool / Transaction / 驱动实现奠基。

12.23 Connection 与 tokio 生态的对比

把 sqlx 的 Connection 放到 tokio 生态里的类似资源抽象对照:

资源类型提供者trait 名关闭方式
TCP 连接tokioAsyncRead + AsyncWriteshutdown().await
HTTP 连接hyper(内部 pooled)连接池内部管理
Redis 连接redis-rsConnectionLike手动 drop
Kafka 连接rdkafka / kafka-rsConsumer / Producer手动 drop 或 close().await
sqlx DB 连接sqlxConnectionclose().await

一个共性连接型资源都在 trait 上暴露 close / shutdown 方法——因为 Drop 不能做 async。sqlx 的 Connection 属于这个家族——设计风格和其他 Rust async 连接库一致。

一个不同其他库的 Connection 多数不暴露 statement cache / buffer shrink 这类方法——因为它们的协议不走"prepared statement"路径(HTTP 无状态、Redis 每次发 command、Kafka 长连接但不预处理)。只有 SQL DB 有这条需求——prepared statement 是 SQL 协议特有的 feature。

这条对比让你理解 Connection trait 的独特职责——它不只是"能收发数据"的 socket wrapper,而是"能跨多条命令保持状态"的对话管理者。SQL 协议的特殊性(事务、prepared、session-level 设置)决定了 Connection trait 需要比一般连接 trait 更丰富的方法集。这也是 sqlx-core 的 Connection 有 14 个方法、而 tokio 的 AsyncRead+AsyncWrite 只有 4 个方法的原因——抽象丰富度对应被抽象领域的复杂度

12.24 本章收尾的六个自检题

读完本章,测试你对 Connection 的理解:

  1. close()drop() 的区别是什么?——前者发 graceful 消息、后者只关 socket。Drop 不能 async 所以不能发消息。
  2. 为什么 SQLite 需要 worker 线程而 Postgres 不需要?——SQLite C API 同步、阻塞 Tokio runtime;Postgres 协议天然异步,直接 tokio::net::TcpStream。
  3. statement_cache_capacity 调大调小的权衡?——大容量 miss 率低但 server 资源占用高;小容量反之。默认 100 是大多数场景的 sweet spot。
  4. 用户什么时候应该显式调 ping()?——几乎从不直接调——它是 Pool 的 test_before_acquire 机制内部用的。
  5. begin()begin_with("BEGIN ISOLATION LEVEL ...") 的区别?——前者用默认 BEGIN、后者用自定义语句——典型用于指定事务隔离级别。
  6. PgConnection 为什么内层包 Box<PgConnectionInner>?——避免 PgConnection 本身 stack size 大(几百字节)——Box 化让它只有指针大小、移动便宜。

能回答这六题说明你对本章的核心概念掌握得不错。如果对某一条不确定,翻回对应节再读一次——Connection 是 sqlx 底层资源抽象的基石,它值得被理解得非常精确

12.25 Connection 层面的调优 checklist

生产环境中一些 Connection 层面能做的调优:

1. statement_cache_capacity 按业务规模调。默认 100 够大多数中型服务;上千种 SQL 模式的大型服务改到 500-1000。改之前测量命中率——通过 cached_statements_size() 监控。

2. 开 log_slow_statements 帮助捕获慢查询。默认 1 秒阈值可以缩到 100ms(对 OLTP 场景合适)。生产日志里看到 WARN 级别的 slow query 比等用户投诉快得多。

3. 关闭 log_statements 在生产。默认 Debug 级别在本地好用;生产每条 SQL 都记日志量巨大、大部分没价值。设 LevelFilter::Off——只靠 slow_statements 抓异常。

4. 给 PgConnectOptions 设 application_name。让 Postgres 的 pg_stat_activity 能区分不同服务的连接——监控连接排查必备。

5. 用 Unix socket 代替 TCP(本机 DB 场景)。postgres:///var/run/postgresql 之类的 URL 走 Unix domain socket——比 localhost TCP 快 2-3 倍(省 TCP 栈开销)。

6. 批量操作后 shrink_buffers。一条 10MB 结果集 fetch 后连接 buffer 保持 10MB——归还 Pool 前调 shrink_buffers 让下次 acquire 从小 buffer 开始。

7. 关键路径考虑关 pingtest_before_acquire 默认开着、每次 acquire 多 1-5ms。延迟敏感 API 路径可以 .test_before_acquire(false)——靠 acquire 失败后 Pool 自动重建兜底。

8. 连接超时合理配置connect_timeout(建立阶段)和 idle_timeout(空闲 Pool 保留)默认值太宽松——生产建议 5 秒 / 10 分钟。

这八条 checklist 是从真实生产 sqlx 部署里总结的调优要点。不是每条都要做——按业务场景选 3-5 条合适的即可。过度调优反而可能引入问题(比如 statement cache 过大撑爆 server 资源)。按实际指标驱动调优才是可持续的做法。

12.26 Connection 作为第四部分开篇的意义

第四部分"连接与事务"从本章开始,走向 13-15 章的 Pool / Transaction。Connection 作为开篇的意义是建立底层资源的基础认知——后面每一章都假设你理解 Connection 的基本生命周期。

  • 第 13 章 Pool 外部 API 建立在"怎么把一堆 Connection 组织起来"上。
  • 第 14 章 Pool 内部 讲 idle queue、semaphore、驱逐策略——所有这些都是管理 Connection 实例
  • 第 15 章 Transaction 讲事务 RAII guard——它持有 &mut Connection 让事务和连接绑定。

如果你跳过本章直接读后面几章,会遇到大量"&mut conn.begin() 为啥这样"或"close_hard 在哪里被 Drop 调"的困惑——那些答案都在本章。Connection 是基础、Pool/Transaction 是上层管理 ——先懂基础才能真正懂管理。

读完本章你应该能回答:

  • 一条数据库连接的完整生命周期是什么?
  • close / close_hard / drop 三者的实际区别?
  • 为什么 SQLite 连接需要 worker 线程?
  • Connection trait 的哪些方法是用户直接调、哪些是 Pool 间接调?
  • 生产环境里连接建立的协议握手大概要多久?

如果这些都清楚,你就掌握了本章核心——可以安心进入第 13 章 Pool 了。

至此第 12 章结束。Connection 层的每个字节、每条协议消息、每个 trait 方法都拆开了。下一章是本书最热闹的话题之一——Pool——sqlx 生产部署里几乎每个问题(连接数、超时、排队)都会和 Pool 有关。读完 Pool 你对 sqlx 的生产运维能力会显著上一个台阶。

一个章节的价值不仅在于内容本身——更在于它为后续章节打下的理解基础。第 12 章 Connection 就是这样一块基础——它讲的不仅是"Connection trait 有哪些方法",而是"一条数据库连接在 Rust async 世界里的完整角色"。掌握这条理解,你看懂第 13-15 章的任何一处细节都会轻松许多。

基于 VitePress 构建