sqlx 源码解析
全卷只对着一个版本写:
| crate | 版本 | tag | commit | 日期 |
|---|---|---|---|---|
sqlx(及 sqlx-core、sqlx-postgres、sqlx-macros、sqlx-macros-core、sqlx-cli) | 0.9.0 | v0.9.0 | 75bc048 | 2026-05-21(CHANGELOG 记的发布日期是 2026-05-06) |
0.9.0 发布时仓库从 launchbadge/sqlx 转到了 transact-rs/sqlx,本卷的源码链接都指向后者的 tag。工作区里的 crate 同版本发布、互相用 =0.9.0 锁住;各 crate 管什么、谁依赖谁,见源码地图。sqlx 支持 Postgres、MySQL / MariaDB 和 SQLite,本卷只深入 Postgres 驱动 sqlx-postgres,另外两个只在对比时提到。
sqlx 的 README 第一句是「SQLx is an async, pure Rust SQL crate featuring compile-time checked queries without a DSL」(原文在 pure Rust 后面有一个脚注:SQLite 驱动调的是 C 库,是例外)。它不提供 ORM,也不提供拼查询的 DSL:查询就是一段 SQL 字符串,query! 宏在编译时连上开发数据库,让数据库自己检查这段 SQL,拿回参数和结果列的类型,生成对应的 Rust 代码。运行时那一侧,Postgres 的线协议用纯 Rust 实现,每条查询默认变成预备语句并按连接缓存,连接池也在库里。本站不直接调 sqlx,而是经 sea-orm 1.1.20 用 sqlx 0.8.6:sea-orm 生成的每一条 SQL,最后都走这一卷讲的那条路径。
全卷沿三条线展开:
- 查询主线(一次
fetch_one):query_as("... where id = $1").bind(id).fetch_one(&pool)。bind把值编码进PgArguments;&Pool借一条连接,交给PgConnection::run;语句缓存没命中就发Parse+Describe+Sync拿回参数和列的类型,然后Bind+Execute+Close+Sync一次发出;每条DataRow包成PgRow,FromRow和Decode把它变成 Rust 值。 - 连接池主线(一次
acquire和归还):在acquire_timeout内拿一个信号量许可,弹一条空闲连接并 ping,或者新建一条;PoolConnection被 drop 时 spawn 一个任务,再 ping 一次、把连接放回空闲队列、释放许可;后台任务按idle_timeout和max_lifetime回收连接。 - 编译期检查主线(一次
query!):过程宏读DATABASE_URL,编译时连库(没有就读.sqlx里cargo sqlx prepare生成的 JSON),对 SQL 做describe,用pg_attribute和EXPLAIN推断每列可不可能是 NULL,生成参数类型检查和一个匿名的Record结构体。
三条线每一步对应的源码和页面,见怎么读这一卷。
flowchart TB intro[00 导读] --> overview[01 全景] overview --> core[02 核心抽象] core --> pg["03 Postgres 驱动"] core --> pool[04 连接池] pg --> tx["05 事务与迁移"] pool --> tx pg --> macros[06 编译期宏] tx --> design[07 设计取舍] macros --> design design --> pit[08 常见坑] pit --> site[09 在本站中] site --> hands[10 动手]
实验环境
关于行为的说法(发了哪几条协议消息、语句缓存命中没有、池子里有几条连接、报什么错、rustc 说了什么)都配一个小程序和它的真实输出,平台是 M2 Max、macOS、rustc 1.99.0、Postgres 16.14(本机容器)。看协议消息用一个自己写的 tokio TCP 代理,把客户端和服务端之间的每条消息按类型打出来,代码在一次查询的全过程。sqlx 跑在 tokio 上,讲到 socket、超时、spawn、信号量时链到 tokio 卷,Json<T> 和 .sqlx 文件链到 serde 卷。
各部分
- 00 导读:锁定的版本和仓库的搬迁,三条主线每一步落在哪个函数、阅读路线、怎么看语句日志和协议消息;工作区各 crate 的分工、feature 体系、
rt/怎么在运行时选 tokio。 - 01 全景:sqlx 和 tokio-postgres、diesel、sea-orm 在设计上的分界;用抓包把一次
query_as+bind+fetch_one完整走一遍,第二次执行不再发Parse,只有&str和raw_sql走简单查询。 - 02 核心抽象:本卷重点。
Database与关联类型,Executor与Acquire,Query、QueryAs与 0.9 的SqlSafeStr,Type/Encode/Decode,Row与FromRow,错误。 - 03 Postgres 驱动:建立连接与 SCRAM 认证,协议消息的编解码,扩展查询协议与语句缓存,Postgres 的类型,
describe,LISTEN与COPY。 - 04 连接池:
PoolOptions的各个参数和默认值,acquire的循环,归还与后台维护。 - 05 事务与迁移:
Transaction与保存点、Drop时排队的回滚;migrate!与Migrator。 - 06 编译期宏:
query!的流水线,生成的代码,离线模式与sqlx.toml,#[sqlx::test]。 - 07 设计取舍:把各章的取舍汇总到一起,每条链回有证据的页面。
- 08 常见坑:用报错原文做标题,每个都先跑出最小复现。
- 09 在本站中:本站经 sea-orm 1.1.20 怎么用 sqlx 0.8.6,池参数和 sqlx 默认值的对比;0.8.6 到 0.9.0 的破坏性改动是否会影响本站。
- 10 动手:不依赖 sqlx,从零写一个迷你 Postgres 客户端:启动与认证、简单查询、带一个参数的扩展查询、一个用信号量的最小连接池。