Appearance
第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." —— 数据库连接礼仪的提纯
本章要点
Connectiontrait(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)vsclose_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开事务—— 默认走 ANSIBEGIN;begin_with(statement)允许自定义(比如BEGIN ISOLATION LEVEL SERIALIZABLE)。返回Transaction<'_, DB>一个 RAII guard(第 15 章详细)。transaction(closure)便捷方法——给一个闭包、内部自动 begin / callback / commit-or-rollback。用户无需手动 begin/commit 两个方法。ConnectOptionstrait——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:277的begin_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;
}两个关联类型 Database 和 Options——通过这两者把 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) 做的事:
- 发服务端特定的 "我要走了" 消息:
- Postgres:
Terminate消息(F类型字节、\0载荷)。 - MySQL:
COM_QUIT。 - SQLite:
sqlite3_close()(C API)。
- Postgres:
- 等待服务端处理(Postgres 立即关 socket、MySQL 发 ACK)。
- 关闭 TCP(Rust 端)。
close_hard(self) 做的事:
- 不发协议消息。
- 直接关闭 TCP(让服务端靠 FIN 发现客户端走了)。
为什么需要两个? 源码 close 的文档注释说得很清楚(connection.rs:25-34):
While connections can simply be dropped to clean up local resources, the
Drophandler 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 默认 true(sqlx-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 会 rollbacktransaction 的价值在错误路径——手写版本里 ? 冒泡时如果忘了 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 也清。什么时候用?
- long-running 连接内存占用——一个连接跑一天、缓存上千条 statement——手动 clear 回收。
- schema 变更后——DDL 改了列类型,旧的 prepared statement 基于旧 schema、继续用会"列类型不匹配"。clear 强制下次重新 prepare。
- 测试场景——测试之间清空缓存隔离状态。
大多数生产代码不调——让 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 connect 和 connect_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_url 把 url::Url 解析成 Options。包括解析 scheme、host、port、user:password、database、query string 参数。Postgres 的 PgConnectOptions::from_url(sqlx-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_statements 和 log_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
}两种淘汰情况:
- 缓存满且 key 新——踢最 least recently used 的那条。
- 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_step、sqlite3_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。
代价:
- 每次 query 多一次 channel ping-pong——约 1-10μs 延迟。
- 一个 OS 线程 per connection——100 个连接池占 100 个线程。
收益:
- SQLite 的同步 API 和 async 世界解耦——用户代码里 SQLite 看起来和 Postgres/MySQL 一样异步。
- 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 对照
总结三家的结构差异:
| 维度 | PgConnection | MySqlConnection | SqliteConnection |
|---|---|---|---|
| 核心 I/O | PgStream(TCP/UDS + TLS) | MySqlStream | ConnectionWorker(OS 线程 + channel) |
| 状态字段 | Inner 十字段(transaction / cache 等) | 类似结构 | Worker 代理 |
| statement cache | LRU(默认 100) | LRU(默认 100) | 有(§12.13 worker 内部) |
| 类型缓存 | 三张 HashMap(OID 等) | 较少(MySQL 类型系统简单) | 无 |
| close 消息 | Terminate | COM_QUIT | sqlite3_close() C 调用 |
| ping 消息 | empty simple query | COM_PING | sqlite3_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 借了 connTransaction 持有 &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 和三家实现的生命周期完整拆开:
- 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 家族串起来。
- close vs close_hard(§12.3)—— 前者发 graceful 消息、后者只发 TCP FIN。Drop 走等价 close_hard 路径——因为 Drop 不能 async。
- ping 协议级探活(§12.4)—— Postgres empty query、MySQL COM_PING、SQLite 内部状态。
- transaction 便捷方法(§12.5)——闭包 + 自动 commit/rollback,比手写
begin / commit更安全(错误路径显式)。 - cached_statements_size / clear(§12.6)—— 受 HasStatementCache 守护,SQLite 不可见。
- shrink_buffers 主动释放(§12.7)—— 避免连接内存占用持续膨胀。用户需显式调。
- connect / connect_with(§12.8)—— URL 解析 vs 详细 Options 配置两条路径。
- ConnectOptions trait(§12.9)—— FromStr / Clone / Debug / Send / Sync super-trait;from_url / connect / log_* 方法。
- LogSettings(§12.10)—— statements_level + slow_statements_level + slow_statements_duration。
- PgConnection 内部十字段(§12.11)—— Box
<Inner>让 stack size 稳定;字段包括 stream、statement cache、type cache、transaction state、log_settings。 - StatementCache LRU(§12.12)—— 默认容量 100,满了踢最老、同 key 替换返回旧值供 server-side close。
- 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 serverConnection 在这条栈里扮演"资源对等体"——上层(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 连接 | tokio | AsyncRead + AsyncWrite | shutdown().await |
| HTTP 连接 | hyper | (内部 pooled) | 连接池内部管理 |
| Redis 连接 | redis-rs | ConnectionLike | 手动 drop |
| Kafka 连接 | rdkafka / kafka-rs | Consumer / Producer | 手动 drop 或 close().await |
| sqlx DB 连接 | sqlx | Connection | close().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 的理解:
close()和drop()的区别是什么?——前者发 graceful 消息、后者只关 socket。Drop 不能 async 所以不能发消息。- 为什么 SQLite 需要 worker 线程而 Postgres 不需要?——SQLite C API 同步、阻塞 Tokio runtime;Postgres 协议天然异步,直接 tokio::net::TcpStream。
statement_cache_capacity调大调小的权衡?——大容量 miss 率低但 server 资源占用高;小容量反之。默认 100 是大多数场景的 sweet spot。- 用户什么时候应该显式调
ping()?——几乎从不直接调——它是 Pool 的 test_before_acquire 机制内部用的。 begin()和begin_with("BEGIN ISOLATION LEVEL ...")的区别?——前者用默认BEGIN、后者用自定义语句——典型用于指定事务隔离级别。- 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. 关键路径考虑关 ping。test_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 章的任何一处细节都会轻松许多。