- 数据库
- 后端
【免费下载链接】sqlx
🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite.
SQLx 是一个面向 Rust 的异步 SQL 工具包,主打"无需 DSL 的编译期查询校验",同时支持 PostgreSQL、MySQL/MariaDB 与 SQLite。本文以官方 README 为主线,结合仓库源码,系统讲解依赖与 feature 配置、连接池、查询 API、query!宏的编译期校验原理,以及配套的 sqlx-cli 离线构建工作流,帮助你从零搭建一个类型安全、可并发的数据库应用。
项目概览:SQLx 到底是什么
SQLx 的核心定位是async、pure Rust 的 SQL 库,它不提供 ORM 式的对象映射 API,而是直接以 SQL 字符串为输入,通过宏在编译期让数据库本身帮你验证查询。README 中概括了它的几大设计取向:
- 真正异步:从底层起基于
async/await构建,最大化并发能力。 - 编译期检查查询(可选):即
query!系列宏,见下文"SQLx 不是 ORM"一节。 - 数据库无关:支持 [PostgreSQL]、[MySQL]、[MariaDB]、[SQLite];MSSQL 在 0.7 版本之前曾被支持,之后被移除,等待官方驱动重写。
- 纯 Rust:Postgres 与 MySQL/MariaDB 驱动用纯 Rust 编写,且使用零
unsafe代码(SQLite 例外,见"安全承诺"一节)。 - 运行时无关:兼容 [
async-std]、[tokio]、[actix] 等运行时,以及 [native-tls]、[rustls] 两种 TLS 后端。
此外还有一批面向工程化的能力,均有对应源码实现:
- 内建连接池
sqlx::Pool,实现位于 sqlx-core/src/pool/mod.rs。 - 行流式读取:数据从数据库异步读出,按需解码。
- 自动语句准备与缓存:使用高层查询 API(
sqlx::query)时,语句按连接准备并缓存。 - 简单(非 prepared)查询执行:同样支持批量执行并返回所有语句的结果。
- TLS 支持:MySQL/MariaDB 与 PostgreSQL 均支持(需开启对应 TLS feature)。
- 异步通知:PostgreSQL 的
LISTEN/NOTIFY。 - 嵌套事务:支持保存点(savepoint)。
Any驱动:可在运行时按 URL scheme 切换底层数据库驱动。
快速上手:安装与 Cargo Feature 组合
SQLx 兼容 [async-std]、[tokio]、[actix] 运行时,以及 [native-tls]、[rustls] TLS 后端。添加依赖时,必须选择一个"runtime + tls"的组合,README 给出了完整的示例清单:
# Cargo.toml [dependencies] # PICK ONE OF THE FOLLOWING: # tokio (no TLS) sqlx = { version = "0.9", features = [ "runtime-tokio" ] } # tokio + native-tls sqlx = { version = "0.9", features = [ "runtime-tokio", "tls-native-tls" ] } # tokio + rustls with ring and WebPKI CA certificates sqlx = { version = "0.9", features = [ "runtime-tokio", "tls-rustls-ring-webpki" ] } # tokio + rustls with ring and platform's native CA certificates sqlx = { version = "0.9", features = [ "runtime-tokio", "tls-rustls-ring-native-roots" ] } # tokio + rustls with aws-lc-rs sqlx = { version = "0.9", features = [ "runtime-tokio", "tls-rustls-aws-lc-rs" ] } # async-std (no TLS) sqlx = { version = "0.9", features = [ "runtime-async-std" ] } # async-std + native-tls sqlx = { version = "0.9", features = [ "runtime-async-std", "tls-native-tls" ] } # async-std + rustls with ring and WebPKI CA certificates sqlx = { version = "0.9", features = [ "runtime-async-std", "tls-rustls-ring-webpki" ] ] # async-std + rustls with ring and platform's native CA certificates sqlx = { version = "0.9", features = [ "runtime-async-std", "tls-rustls-ring-native-roots" ] } # async-std + rustls with aws-lc-rs sqlx = { version = "0.9", features = [ "runtime-async-std", "tls-rustls-aws-lc-rs" ] }运行时与 TLS feature 的取舍
为保持向后兼容,runtime 与 TLS feature 既可以合并成一个 feature 使用,也可以分开声明。面向未来兼容,应使用分开的 runtime 与 TLS feature,因为合并型组合 feature 未来可能被移除。从仓库 Cargo.toml 的 feature 定义可以看到,runtime-tokio、runtime-async-std等独立 feature 与tls-native-tls、tls-rustls-*系列是平级拆分的。
运行时选择的具体规则见 src/lib.md:
- 若同时启用多个运行时 feature,当前线程存在 Tokio 上下文(即
tokio::runtime::Handle::try_current()返回Ok)时使用 Tokio,否则使用 async-std。 - SQLx 不再对"零个或多个运行时 feature"产生编译错误(方便库作者二次封装),但几乎任何异步 API 在未启用至少一个运行时 feature 时调用都会 panic。唯一的例外是 SQLite 驱动(本身与运行时无关),不过
SqlitePool的 timeout 与内部管理任务仍需要运行时支持。 - TLS 方面,
tls-native-tls使用操作系统原生 TLS(macOS 用 SecureTransport、Windows 用 SChannel、其他平台用 OpenSSL);tls-rustls是跨平台实现,仅支持 TLS 1.2 与 1.3。若使用 rustls 遇到HandshakeFailure,通常意味着数据库服务器不支持这些新版本,可尝试启用或切换tls-native-tls。同时启用多个 TLS feature 时tls-native-tls优先。若连接配置需要 TLS 升级但未启用 TLS feature,连接会直接返回错误。
Cargo Feature 全表
README 完整列出了各 feature 的语义,整理如下:
| Feature | 说明 |
|---|---|
runtime-async-std | 使用async-std运行时,不启用 TLS 后端 |
runtime-tokio | 使用tokio运行时,不启用 TLS 后端(Actix-web 与 Tokio 完全兼容,无需单独的 runtime feature) |
tls-native-tls | 使用native-tls后端(*nix 用 OpenSSL、Windows 用 SChannel、macOS 用 Secure Transport) |
tls-rustls | 使用rustls后端(跨平台,仅支持 TLS 1.2 与 1.3) |
tls-rustls-aws-lc-rs | 使用rustls+aws-lc-rs加密提供者 |
postgres | 支持 Postgres 数据库 |
mysql | 支持 MySQL/MariaDB(非 TLS 场景的 RSA 认证需要额外开启mysql-rsa) |
mysql-rsa | 在 TLS 关闭时为caching_sha2_password/sha256_password启用 RSA 密码加密;仅在必须无 TLS 连接且服务器要求 RSA 认证时开启,优先建议使用 TLS |
mssql | 支持 MSSQL 数据库 |
sqlite | 支持内嵌 SQLite,捆绑并静态链接 SQLite |
sqlite-unbundled | 同上,但链接系统 SQLite 而非捆绑版本(可独立升级或使用 fork 版本;系统需安装 SQLite 或在构建时提供库路径;SQLite 过旧(低于 3.20.0)可能链接失败;因使用 bindgen 可能增加构建时间) |
sqlite-preupdate-hook | 启用 SQLite 的 preupdate hook API(默认不开启;与sqlite-unbundled联用可能因系统 SQLite 版本不支持而链接失败) |
any | 支持可在运行时代理到具体驱动的Any驱动 |
derive | 启用 derive 宏家族:FromRow、Type、Encode、Decode |
macros | 启用query*!宏,实现编译期检查查询 |
migrate | 启用迁移管理与migrate!宏,支持编译期内嵌迁移 |
uuid | 支持 UUID 类型 |
chrono | 支持chronocrate 的日期时间类型 |
time | 支持timecrate 的日期时间类型(与chrono二选一;若两者都启用,query!宏默认偏好time) |
bstr | 支持bstr::BString |
bigdecimal | 使用bigdecimalcrate 支持NUMERIC |
rust_decimal | 使用rust_decimalcrate 支持NUMERIC |
ipnet | 支持 Postgres 的INET/CIDR(基于ipnet) |
ipnetwork | 同上(基于ipnetwork) |
json | 使用serde_json支持JSON/JSONB(Postgres) |
注意 README 中特别说明:离线模式(offline mode)现已默认启用,详见 sqlx-cli/README.md 的离线构建章节。另外从仓库根 Cargo.toml 可以看到sqlx的默认 features 为["any", "macros", "migrate", "json"],即默认就带上Any驱动、查询宏、迁移能力与 JSON 支持。
编译期校验的设计哲学:SQLx 不是 ORM
README 用一个独立章节强调了这一设计立场:SQLx 支持编译期检查查询,但不是通过提供 Rust API 或 DSL 来构建查询,而是提供以普通 SQL 为输入的宏,并确保这些 SQL 对你的数据库是有效的。其工作方式是:在编译时连接你的开发数据库,让数据库自身去校验(并返回相关信息)SQL 查询。
这会带来两个值得注意的推论:
- 由于 SQLx 从不自行解析 SQL 字符串,任何开发数据库能接受的语法都能使用,包括数据库扩展新增的语法。
- 不同数据库允许查询方获取的信息量不同,因此查询宏能做的校验程度取决于数据库。
这一机制对应宏实现位于 sqlx-macros-core/src/query(含输入解析、数据库连接、元数据缓存与输出生成等模块)。如果你需要的是(异步)ORM,README 建议查阅官方 Ecosystem wiki 页(如ormx、SeaORM等)。
基础用法:连接、连接池与查询
仓库在 examples 目录提供了多数据库、多场景的完整示例(Postgres/MySQL/SQLite 的 todos、事务、监听、多数据库、多租户等),下面结合 README 的 Quickstart 与示例源码展开。
Quickstart
README 给出了一个最小可运行的程序:
use sqlx::postgres::PgPoolOptions; // use sqlx::mysql::MySqlPoolOptions; // etc. #[async_std::main] // Requires the `attributes` feature of `async-std` // or #[tokio::main] // or #[actix_web::main] async fn main() -> Result<(), sqlx::Error> { // Create a connection pool // for MySQL/MariaDB, use MySqlPoolOptions::new() // for SQLite, use SqlitePoolOptions::new() // etc. let pool = PgPoolOptions::new() .max_connections(5) .connect("postgres://postgres:password@localhost/test").await?; // Make a simple query to return the given parameter (use a question mark `?` instead of `$1` for MySQL/MariaDB) let row: (i64,) = sqlx::query_as("SELECT $1") .bind(150_i64) .fetch_one(&pool).await?; assert_eq!(row.0, 150); Ok(()) }注意占位符的差异:Postgres 使用$1,MySQL/MariaDB 使用?。
建立连接
单个连接可通过任意数据库连接类型调用connect()建立(src/lib.rs 统一导出了各数据库的连接与池类型):
use sqlx::Connection; let conn = SqliteConnection::connect("sqlite::memory:").await?;但实际项目中通常建议改用连接池(sqlx::Pool),以调控应用占用服务端连接的数量:
let pool = MySqlPool::connect("mysql://user:pass@host/database").await?;连接池:为什么应该用 Pool
连接池模块的源码文档(sqlx-core/src/pool/mod.rs)详细解释了"为什么要用池":
- 开连接的代价高:对 SQLite 意味着大量文件系统请求与内存分配;对服务端数据库则涉及 DNS 解析、新 TCP 连接、缓冲区分配,以及复杂的握手(认证、连接参数协商、加密隧道升级)。服务端往往还要为每个连接派生线程/进程。
- 连接上限:MySQL/Postgres 等服务器通常对并发连接数设硬上限(如 Postgres 默认约 100,保留 3 个给超级用户)。用池可以让客户端在连接耗尽时进入公平等待队列,而不是直接报错或产生 500。
- 资源复用:prepared statement 与查询计划缓存通常按连接隔离,池促成了连接的复用,从而摊销准备语句的开销。
Pool本身是Send + Sync + Clone的引用计数句柄(内部为Arc<PoolInner<DB>>),建议在应用/服务启动时创建一次,然后共享给所有任务。&Pool可以直接传给任何需要Executor的地方,自动为你借出连接:
sqlx::query("DELETE FROM table").execute(&pool).await?;池的默认参数定义在 sqlx-core/src/pool/options.rs 的PoolOptions::new()中,常用默认值如下:
| 配置项 | 默认值 | 语义 |
|---|---|---|
max_connections | 10 | 池可维护的最大连接数(生产应用通常需要调高) |
min_connections | 0 | 池预建并尽力维持的最小连接数 |
test_before_acquire | true | 借出连接前调用Connection::ping校验健康度 |
acquire_timeout | 30 秒 | acquire()等待连接的总时长上限,超时返回PoolTimedOut |
acquire_slow_threshold | 2 秒 | 超过该阈值视为"慢获取"并记日志 |
acquire_slow_level | Warn | 慢获取的日志级别 |
acquire_time_level | Off | 普通获取的日志级别(默认关闭) |
idle_timeout | 10 分钟 | 空闲连接在池中停留的上限,超时关闭(按用量计费的服务可省钱) |
max_lifetime | 30 分钟 | 连接最大生命周期,到期后回收(避免服务端内存/资源泄漏) |
fair | true | acquire()是否公平(先到先得) |
此外PoolOptions还支持三个回调:after_connect(连接建立后执行,如设置application_name、search_path等连接参数)、before_acquire(借出前对空闲连接执行检查,返回Ok(true)才借出)、after_release(归还时处理)。由于 Rust 无法直接表达带高阶生命周期的闭包返回类型,这些回调统一要求返回Box::pin的 future。
关于优雅关闭,Pool::close().await会唤醒所有等待者、拒绝后续acquire,并等待所有连接归还后向服务器发送关闭消息;由于没有 async drop,仅靠 drop 最后一个Pool句柄可能不会立刻通知服务端(服务端要等 TCP keepalive 超时),频繁创建销毁池还可能触发连接上限错误,因此文档明确建议在关闭阶段调用.close().await。
查询:prepared 与 unprepared
SQL 中的查询可分为prepared(参数化)与unprepared(简单)两类:
- Prepared:查询计划会被缓存,使用二进制通信(带宽更低、解码更快),通过参数绑定避免 SQL 注入。
- Unprepared:简单直接,仅用于 prepared 无法工作的场景,如
PRAGMA、SET、BEGIN等数据库命令。
SQLx 对两者都支持。在 SQLx 中,&str被视为 unprepared 查询,Query/QueryAs结构体被视为 prepared 查询:
// low-level, Executor trait conn.execute("BEGIN").await?; // unprepared, simple query conn.execute(sqlx::query("DELETE FROM table")).await?; // prepared, cached query应优先使用高层query接口,类型上有 finalizer(终结器),省去手动包 executor 的麻烦:
sqlx::query("DELETE FROM table").execute(&mut conn).await?; sqlx::query("DELETE FROM table").execute(&pool).await?;execute返回受影响行数并丢弃所有结果;另有fetch、fetch_one、fetch_optional、fetch_all用于取回结果。sqlx::query返回的Query会产生Row<'conn>,列值可用row.get()按序号或列名访问;由于Row持有连接的不可变借用,同一时刻只能存在一个Row。
fetch返回类流类型,可逐行迭代:
// provides `try_next` use futures_util::TryStreamExt; // provides `try_get` use sqlx::Row; let mut rows = sqlx::query("SELECT * FROM users WHERE email = ?") .bind("user@example.com") .fetch(&mut conn); while let Some(row) = rows.try_next().await? { // map the row into a user-defined domain type let email: &str = row.try_get("email")?; }把行映射为领域类型有两种惯用写法:
let mut stream = sqlx::query("SELECT * FROM users") .map(|row: PgRow| { // map the row into a user-defined domain type }) .fetch(&mut conn);#[derive(sqlx::FromRow)] struct User { name: String, id: i64 } let mut stream = sqlx::query_as::<_, User>("SELECT * FROM users WHERE email = ? OR name = ?") .bind("user@example.com") .bind("example_username") .fetch(&mut conn);若只需要单个结果,用fetch_one(必需结果)或fetch_optional(可选结果)。
事务
事务通过Connection::begin/Pool::begin开启,结束时应调用commit或rollback;若两者都未调用就离开作用域,drop时会自动回滚(见 sqlx-core/src/transaction.rs)。事务本身也实现了Executor,可以直接在其上执行查询:
let mut tx = conn.begin().await?; let result = sqlx::query("DELETE FROM \"testcases\" WHERE id = $1") .bind(id) .execute(&mut *tx) .await? .rows_affected(); tx.commit().await嵌套事务通过保存点(savepoint)实现:TransactionManager的get_transaction_depth定义了深度语义——0 级无事务、1 级有活动事务、2 级及以上表示事务内建立了保存点。README 强调的"嵌套事务 + 保存点"能力即来自这一机制。
编译期校验的查询宏:query! 与 query_as!
这是 SQLx 最具特色的能力。使用sqlx::query!宏可以在编译期获得 SQL 的语法与语义双重校验,输出为匿名记录类型,每个 SQL 列对应一个 Rust 字段(必要时使用 raw identifier):
let countries = sqlx::query!( " SELECT country, COUNT(*) as count FROM users GROUP BY country WHERE organization = ? ", organization ) .fetch_all(&pool) // -> Vec<{ country: String, count: i64 }> .await?; // countries[0].country // countries[0].count与query()的差异:
- 绑定参数必须一次性全部给出,且编译期会校验参数的数量与类型是否正确。
- 输出类型是匿名记录,上例中类型形如
{ country: String, count: i64 }。 - 构建时必须设置
DATABASE_URL环境变量指向一个可供准备语句的数据库:该库不必有数据,但必须与运行时连接的库同类型(MySQL、Postgres 等)且同 schema。
为了方便,可以用 [.env文件](基于dotenvycrate,格式与dotenv相同)持久化DATABASE_URL:
DATABASE_URL=mysql://localhost/my_databasequery!()最大的缺点是输出类型无法命名(Rust 尚未官方支持匿名记录),因此提供了query_as!()宏,除可命名输出类型外其余行为基本一致:
// no traits are needed struct Country { country: String, count: i64 } let countries = sqlx::query_as!(Country, " SELECT country, COUNT(*) as count FROM users GROUP BY country WHERE organization = ? ", organization ) .fetch_all(&pool) // -> Vec<Country> .await?; // countries[0].country // countries[0].count各数据库下的占位符与类型差异
从仓库示例可看到不同数据库的细节差异:
- Postgres使用
$1占位符,且可用RETURNING id直接拿回插入的 id(见 examples/postgres/todos/src/main.rs)。 - MySQL/MariaDB使用
?占位符,插入后通过.last_insert_id()获取自增 id;且 MySQL 的布尔值实际存储为TINYINT(1)/i8,0 为 false、非 0 为 true,因此读取done字段时要与!= 0比较(见 examples/mysql/todos/src/main.rs)。
以 Postgres todos 为例,一个完整的 CRUD 流程长这样:
async fn add_todo(pool: &PgPool, description: String) -> anyhow::Result<i64> { let rec = sqlx::query!( r#" INSERT INTO todos ( description ) VALUES ( $1 ) RETURNING id "#, description ) .fetch_one(pool) .await?; Ok(rec.id) } async fn complete_todo(pool: &PgPool, id: i64) -> anyhow::Result<bool> { let rows_affected = sqlx::query!( r#" UPDATE todos SET done = TRUE WHERE id = $1 "#, id ) .execute(pool) .await? .rows_affected(); Ok(rows_affected > 0) }加速增量编译
编译期校验在编译期做了不少工作。README 建议在Cargo.toml中加入以下配置,让cargo check、cargo build的增量构建显著变快:
[profile.dev.package.sqlx-macros] opt-level = 3离线模式(offline mode)
如果项目代码(数据库访问部分)没有改动,却仍需要开发数据库才能编译,可以启用"离线模式",用 sqlx 命令行工具把 SQL 查询分析结果缓存下来。详细步骤见 sqlx-cli/README.md:
- 保存查询元数据:
cargo sqlx prepare(必须通过cargo sqlx调用)。它会将查询元数据写入当前目录的.sqlx;若工作区有多个 crate 使用查询宏,加--workspace会在工作区根生成统一的.sqlx目录。把该目录提交进版本控制后,构建项目不再需要活动的数据库连接。 - 构建:之后正常
cargo build即可。
CI 场景可用cargo sqlx prepare --check(或--check --workspace):若.sqlx中的数据与当前数据库 schema 或项目中的查询不一致,会以非零退出码结束。
另外,DATABASE_URL环境变量的优先级高于.sqlx目录——只要它存在,SQLx 默认仍会尝试连数据库构建。若想强制离线,可设置SQLX_OFFLINE=true(也可写入.env文件作为默认),cargo sqlx prepare本身不受影响、仍会正常连库。对于 feature flag 或测试中的查询,可通过给 cargo 传参来让 prepare 覆盖到,例如cargo sqlx prepare -- --all-targets --all-features。
进阶能力:Listen/Notify、Any 驱动与迁移
- PostgreSQL 异步通知:通过
LISTEN/NOTIFY实现,仓库示例见 examples/postgres/listen。 - Any 驱动:
AnyPool依据 URL scheme 在运行时选择底层驱动,相关实现位于 src/any 与各驱动的any.rs。 - 迁移(migration):sqlx-cli 提供
sqlx migrate add(生成migrations/<timestamp>-<name>.sql)、sqlx migrate run(对比数据库迁移历史并执行待应用脚本)、--source指定迁移目录、-r生成可逆迁移(.up.sql/.down.sql,之后所有迁移都会是可逆的)以及sqlx migrate revert。数据库的创建/删除可用sqlx database create/sqlx database drop,所有命令都通过--database-url或DATABASE_URL(环境变量或.env文件)提供连接信息。仓库各示例目录下都有真实的迁移文件可参考,如 examples/postgres/todos/migrations/20200718111257_todos.sql。
安全承诺与许可
README 与 src/lib.rs 都确认:SQLx 使用#![forbid(unsafe_code)]保证核心代码 100% Safe Rust。启用sqlitefeature 后会降级为#![deny(unsafe_code)]并仅对sqlx::sqlite模块放行unsafe——因为 SQLite 是嵌入式数据库,纯 Rust 只能靠移植整个 SQLite 实现,SQLx 的实际做法是通过libsqlite3-sys直接调用 SQLite3 C API。SQLx 官方欢迎社区对 unsafe 用法进行审计(相关调用点集中在 sqlx-sqlite 的语句句柄与连接实现中)。
SQLx 采用Apache-2.0 或 MIT双许可,以你选择的方式授权,许可文本见 LICENSE-APACHE 与 LICENSE-MIT。除非明确另行声明,按 Apache-2.0 定义提交的贡献均按上述双许可授权。
总结
SQLx 的竞争力来自三件事的叠加:纯 Rust 异步驱动(连接、流式读取、池、事务全套齐备)、以数据库为裁判的编译期查询校验(query!/query_as!+ 离线缓存),以及跨数据库、跨运行时、跨 TLS 后端的可组合 feature 体系。上手路径很清晰:先按 README 的组合表配好依赖,用PgPoolOptions之类建池,再用query!享受编译期保障;遇到无库可连的 CI 场景,就把cargo sqlx prepare与SQLX_OFFLINE纳入构建流程。
- 数据库
- 后端
【免费下载链接】sqlx
🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite.
相关推荐
使用 SQLx 管理 Tabby 数据库:从编译期查询校验到迁移工作流
使用 SQLx 管理 Tabby 数据库:从编译期查询校验到迁移工作流 Tabby(Self hosted AI coding assistant)使用 SQL
人工智能大模型本地部署模型推理服务后端RAG交互助手Ajv 异步验证完全指南:用 `$async` 与异步关键词构建数据库查询校验
Ajv 异步验证完全指南:用 $async 与异步关键词构建数据库查询校验 导读 Ajv 不仅是一个同步的 JSON Schema 验证器,还内置了一整套异步验
后端API设计如何用GitHub Insights追踪Rainmeter社区贡献统计:完整指南
如何用GitHub Insights追踪Rainmeter社区贡献统计:完整指南 Rainmeter是一款强大的Windows桌面自定义工具,它让用户能够创建个
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考