SQLx
问题
SQLx 的编译时 SQL 检查是如何实现的?
答案
SQLx 是一个异步、纯 Rust 的 SQL 工具包。核心特性:编译时检查 SQL 语法和类型。
基础 CRUD
use sqlx::{PgPool, Row, FromRow};
use serde::{Deserialize, Serialize};
#[derive(Debug, FromRow, Serialize)]
struct User {
id: i64,
name: String,
email: String,
}
// 查询:编译时检查 SQL 语法和返回类型
async fn find_user(pool: &PgPool, id: i64) -> sqlx::Result<Option<User>> {
sqlx::query_as!(
User,
"SELECT id, name, email FROM users WHERE id = $1",
id
)
.fetch_optional(pool)
.await
}
// 插入
async fn create_user(pool: &PgPool, name: &str, email: &str) -> sqlx::Result<User> {
sqlx::query_as!(
User,
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email",
name,
email
)
.fetch_one(pool)
.await
}
// 动态查询(query_as 不带 !,运行时检查)
async fn search_users(pool: &PgPool, keyword: &str) -> sqlx::Result<Vec<User>> {
sqlx::query_as::<_, User>(
"SELECT id, name, email FROM users WHERE name ILIKE $1"
)
.bind(format!("%{}%", keyword))
.fetch_all(pool)
.await
}
连接池配置
use sqlx::postgres::PgPoolOptions;
let pool = PgPoolOptions::new()
.max_connections(20)
.min_connections(5)
.acquire_timeout(std::time::Duration::from_secs(5))
.idle_timeout(std::time::Duration::from_secs(600))
.connect("postgres://user:pass@localhost/mydb")
.await?;
事务
async fn transfer(pool: &PgPool, from: i64, to: i64, amount: i64) -> sqlx::Result<()> {
let mut tx = pool.begin().await?;
sqlx::query!("UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, from)
.execute(&mut *tx).await?;
sqlx::query!("UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, to)
.execute(&mut *tx).await?;
tx.commit().await?;
Ok(())
}
query! vs query_as! vs query_as
| 宏/函数 | 编译时检查 | 返回类型 |
|---|---|---|
query!() | ✅ | 匿名结构体 |
query_as!() | ✅ | 指定结构体 |
query_as::<_, T>() | ❌ | 指定结构体 |
query() | ❌ | Row |
编译时检查原理
query! 宏在编译时连接数据库(通过 DATABASE_URL),验证 SQL 语法、表/列是否存在、参数类型是否匹配。CI 中可用 sqlx prepare 生成离线缓存,避免编译时需要数据库。
常见面试问题
Q1: SQLx 的编译时检查在 CI 中如何工作?
答案:
- 本地开发时:
query!直连数据库检查 - 提交前:运行
cargo sqlx prepare生成.sqlx/缓存文件 - CI 中:设置
SQLX_OFFLINE=true,使用缓存文件而非连接数据库
Q2: SQLx 如何防止 SQL 注入?
答案:
query! 和 query_as! 使用参数化查询(占位符 $1, $2),参数绝不会被拼接到 SQL 字符串中,从根本上防止 SQL 注入。