SQLx Executor、Query 与连接池
This content is not available in your language yet.
SQLx 把“要执行什么”与“在哪里执行”拆开:Query 保存 SQL 和 arguments,Executor 提供执行/fetch 能力,Pool 通过可复用连接把异步任务接入具体 driver。
先给答案:SQLx 把“要执行什么”与“在哪里执行”拆开:Query 保存 SQL 和 arguments,Exec…
Section titled “先给答案:SQLx 把“要执行什么”与“在哪里执行”拆开:Query 保存 SQL 和 arguments,Exec…”SQLx 把“要执行什么”与“在哪里执行”拆开:Query 保存 SQL 和 arguments,Executor 提供执行/fetch 能力,Pool 通过可复用连接把异步任务接入具体 driver。 正文沿“执行链 -> Pool 的并发模型 -> Executor 的抽象价值”展开:先确认入口和状态归属,再跟踪控制流或数据流的推进,最后落到对外可观察的结果。
主要失效边界集中在“在事务内再用 pool 执行、pool size 太小、持有 PoolConnection 跨大量 await”这些场景。它们破坏的是容量、顺序、并发或生命周期前提;排查时应先确认状态是否仍由正确对象持有,再核对推进条件和清理路径。
query::<DB>(sql) -> Query<'q, DB, Arguments> -> Query::execute / fetch -> Executor<'c>::execute / fetch_many -> Pool acquire -> Connection::execute -> driver protocol坐标:Query 在 sqlx-core/src/query.rs:18;构造函数 query() 在 :653;Query::execute() 在 :185;Executor trait 在 executor.rs:53;Pool 在 pool/mod.rs:260;Pool::connect() 在 :284;Pool::begin() 在 :371;PoolOptions::connect() 在 pool/options.rs:528。
Pool 的并发模型
Section titled “Pool 的并发模型”PoolInner├─ semaphore 限制同时持有的连接数├─ idle connections 可立即复用├─ live connection count├─ waiters 异步等待 permit / connection└─ options acquire timeout、idle/max lifetime、hooks连接池不是简单 Mutex<Vec<Connection>>:任务先受 semaphore 限制,再从 idle 队列取连接;连接归还时需要检查关闭、生命周期、健康状态,失效连接不能重新进入池。PoolConnection 作为 RAII guard,在 drop/close 时归还或关闭底层连接。
Executor 的抽象价值
Section titled “Executor 的抽象价值”| 调用对象 | 语义 |
|---|---|
&Pool<DB> |
自动 acquire、执行后归还 |
&mut Connection |
当前连接直接执行 |
&mut Transaction |
在事务连接上执行 |
PoolConnection<DB> |
持有连接期间多次执行 |
Executor 约束 DB 的 Database 关联类型,使同一个 Query API 可被不同 driver 的连接、pool、transaction 使用。异步生命周期参数 'c 则表达执行期间必须保持 executor 有效。
为什么 acquire 使用异步等待
Section titled “为什么 acquire 使用异步等待”替代方案:池满时阻塞线程等待可用连接。
为什么不行:异步 runtime 的 worker 被阻塞后,持有连接的任务可能无法继续运行并归还连接,形成调度层面的饥饿甚至死锁。
证据:池内部使用 async semaphore;sqlx-core/src/sync.rs:82 的 AsyncSemaphoreReleaser::acquire() 是异步 permit 获取路径,pool options 还提供 acquire timeout。
生命周期策略
Section titled “生命周期策略”连接可能因为 max lifetime、idle timeout、数据库主动断开或健康检查失败而淘汰。池的目标不是保证每个连接永远存在,而是把连接创建成本摊平,并将坏连接隔离在归还边界。
| 场景 | 现象 | 原因 | 规避 |
|---|---|---|---|
| 在事务内再用 pool 执行 | 查询不在当前事务 | pool 会 acquire 另一条连接 | 把 &mut Transaction 传给 query |
| pool size 太小 | acquire timeout | 长事务占满连接 | 缩短事务、设置合理 timeout/size |
持有 PoolConnection 跨大量 await |
其他任务饥饿 | guard 生命周期过长 | 尽量缩小连接持有范围 |
| drop future 取消查询 | 服务端可能已收到部分请求 | 取消不等于数据库回滚 | 对关键写操作使用 transaction 和幂等设计 |
面试锚点
Query、Executor、Pool的职责如何分离?- 为什么池满时必须异步等待?
- pool 与 transaction 执行为何不能混用?