你以为 ORM 只能靠运行才发现 SQL 写错了?今天 SQLx 用编译期检查把这件事彻底变了

写过 Rust 后端的人都踩过这个坑——SQL 字符串写进代码里,到底有没有语法错误、有没有拼错列名、类型对不对得上,只能等运行的时候数据库报错才恍然大悟。特别是 CI 跑完、上了生产,突然一条查询崩了,那感觉就像考试铃响了才发现答案写在草稿纸上。

这件事被 SQLx 0.9(2026 年 5 月发布)彻底原生化了——它让你写 SQL 不用二选一:要么放弃类型安全靠 ORM,要么放弃 SQL 靠 query builder。SQLx 给你第三条路:直接写 SQL,编译期检查

query!宏:连接一次数据库,换一辈子的编译时安心

SQLx 的核心是 query! 宏家族。编译时,它会真正连接你的开发数据库(PostgreSQL / MySQL / SQLite),让数据库本身验证 SQL 语法是否正确、列名存不存在、参数类型和返回值能不能对上。整个过程不需要任何 DSL,直接写原始 SQL 字符串:

let rec = sqlx::query!(
    "SELECT id, name, email FROM users WHERE id = $1",
    user_id
)
.fetch_one(&pool)
.await?;
println!("{}!", rec.name); // name 字段类型在编译时就确定了

如果 SQL 有问题——比如 $1 传了 &str 但数据库期待 i64——cargo build 直接失败,报错信息直接告诉你第几行、哪个字段、类型不匹配。数据库连接只在本地编译时用一次,生产构建不需要。

离线模式:CI 构建不需要数据库也能验证

团队协作时,不能要求每个开发者的机器都连着数据库,CI 服务器更不可能。SQLx 支持离线模式:

# 本地运行,生成 .sqlx/ 缓存目录
cargo sqlx prepare
# 提交 .sqlx/ 到 Git
# CI 中设置环境变量,SQLx 自动使用缓存
SQLX_OFFLINE=true cargo build

每次迁移改了 schema 或 SQL,commit 前跑一次 cargo sqlx prepare --check,CI 就能发现缓存过期、阻止带病代码合并。这个工作流把「SQL 写错了,上线才知道」变成了「commit 没更新缓存,CI 就拦住」。

Rust 工具链视角:比 ORM 快,比 query builder 安全

Rust 后端三大 SQL 方案对比:

SQLx Diesel SeaORM
抽象程度 无——直接写 SQL 类型化 query builder ORM(关系映射)
SQL 检查 编译期(需 DB) 编译期(DSL) 运行时
异步 原生 async/await 同步( diesel-async 有异步) 原生 async/await
学习曲线 只需懂 SQL 需学 DSL 需学 ORM 概念

Diesel 靠 DSL 保证了编译期安全,但写法和 SQL 差异大;SeaORM 帮你省脑子但检查全在运行时。SQLx 的思路是:别假装 SQL 不存在,让它成为一等公民,给你类型安全兜底。

你真正要注意的坑

query! 需要数据库连接才能编译,这意味着没有 .sqlx 缓存时 CI 构建必须有数据库。一个常见误区是以为「编译过了就是安全的」——实际上缓存不更新的话,schema 改了 SQL 没改,还是会放过漏网之鱼。建议把 cargo sqlx prepare --check 写进 CI 的必要步骤,单独一个 job,专门盯缓存有效性。

第二个坑是 nullable 处理:数据库的 nullable 字段映射到 Rust 需要用 Option<T>,如果你没加 ?,编译期会报错,告诉你「这个字段可能是 NULL,你得处理」。这其实是好事,但第一天切过来会多出不少 ? 操作符。

下一步

如果你是 Rust 后端新手,想从零搭一个带数据库的项目:从 cargo install sqlx-cli 开始,跑一次 sqlx database create,然后在迁移文件里把 schema 定好。schema 敲定了,query! 写 SQL 的时候心里就踏实了——写错了 cargo build 会拦住你,不会在凌晨三点让用户看到 500。

评论区

0 条评论

登录后可评论。

铁锈·Rust工具链 16 阅读