Rust + SQLite 完整 CRUD 示例
使用主流 rusqlite 库,支持增删改查,带连接、建表、事务、参数绑定,完整可直接运行。
rusqlite 是 Rust 生态最成熟的 SQLite 绑定库,底层 libsqlite3。
1. Cargo.toml
rust
[package]
name = "sqlite_crud_demo"
version = "0.1.0"
edition = "2021"
[dependencies]
rusqlite = { version = "0.29", features = ["bundled"] } # bundled内置sqlite库,不用系统安装
anyhow = "1.0" # 方便错误处理2. src/main.rs 完整代码
rust
use anyhow::{Context, Result};
use rusqlite::{params, Connection, Row};
/// 用户模型
#[derive(Debug)]
struct User {
id: i64,
name: String,
email: String,
age: Option<i32>,
}
/// 从数据库行映射User
impl User {
fn from_row(row: &Row) -> rusqlite::Result<Self> {
Ok(User {
id: row.get(0)?,
name: row.get(1)?,
email: row.get(2)?,
age: row.get(3)?,
})
}
}
/// 初始化数据库,创建users表
fn init_db(conn: &Connection) -> Result<()> {
conn.execute(
"CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
age INTEGER
)",
(),
)
.context("建表失败")?;
Ok(())
}
/// 新增 Create
fn create_user(conn: &Connection, name: &str, email: &str, age: Option<i32>) -> Result<i64> {
conn.execute(
"INSERT INTO users (name, email, age) VALUES (?1, ?2, ?3)",
params![name, email, age],
)
.context("插入用户失败")?;
// 获取自增id
let new_id = conn.last_insert_rowid();
Ok(new_id)
}
/// 查询单个 Read - 根据id查
fn get_user_by_id(conn: &Connection, user_id: i64) -> Result<Option<User>> {
let mut stmt = conn.prepare("SELECT id, name, email, age FROM users WHERE id = ?1")?;
let user = stmt.query_row(params![user_id], User::from_row);
match user {
Ok(u) => Ok(Some(u)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(e.into()),
}
}
/// 查询全部 Read
fn list_all_users(conn: &Connection) -> Result<Vec<User>> {
let mut stmt = conn.prepare("SELECT id, name, email, age FROM users ORDER BY id")?;
let user_iter = stmt.query_map((), User::from_row)?;
let mut users = Vec::new();
for u in user_iter {
users.push(u.context("读取行数据错误")?);
}
Ok(users)
}
/// 更新 Update
fn update_user_age(conn: &Connection, user_id: i64, new_age: Option<i32>) -> Result<usize> {
let affected = conn.execute(
"UPDATE users SET age = ?1 WHERE id = ?2",
params![new_age, user_id],
)
.context("更新失败")?;
Ok(affected) // 返回受影响行数
}
/// 删除 Delete
fn delete_user(conn: &Connection, user_id: i64) -> Result<usize> {
let affected = conn.execute("DELETE FROM users WHERE id = ?1", params![user_id])
.context("删除失败")?;
Ok(affected)
}
fn main() -> Result<()> {
// 打开sqlite文件,不存在则创建;":memory:" 内存数据库
let conn = Connection::open("demo.db")?;
init_db(&conn)?;
println!("数据库初始化完成");
// 增
let uid = create_user(&conn, "张三", "zhangsan@test.com", Some(22))?;
println!("新增用户 id = {}", uid);
// 查单个
if let Some(u) = get_user_by_id(&conn, uid)? {
println!("查询单条: {:?}", u);
}
// 更新
let cnt = update_user_age(&conn, uid, Some(25))?;
println!("更新影响行数: {}", cnt);
// 查询全部
let all = list_all_users(&conn)?;
println!("全部用户列表: {:#?}", all);
// 删除
let del_cnt = delete_user(&conn, uid)?;
println!("删除影响行数: {}", del_cnt);
// 删除后再看
let after_del = list_all_users(&conn)?;
println!("删除后列表: {:#?}", after_del);
Ok(())
}运行
bash
cargo run运行后项目目录生成 demo.db SQLite数据库文件,可以用 DB Browser for SQLite 打开查看。
关键知识点说明
- 参数绑定
?1 ?2千万不要字符串拼接SQL,使用params![],防止SQL注入。 - NULL 处理
Option<i32>对应数据库 NULL,数据库可为空字段一律用 Option。 - 内存数据库
Connection::open(":memory:"),程序结束数据消失,单元测试很方便。 - 事务示例
rust
fn transaction_demo(conn: &Connection) -> Result<()> {
let tx = conn.transaction()?; // 开启事务
tx.execute("INSERT INTO users(name,email) VALUES(?1,?2)", params!("A","a@x.com"))?;
tx.commit()?; // 提交;tx.rollback() 回滚
Ok(())
}rusqlite 的事务非常重要,批量操作包裹事务速度提升几十倍。
搭配 Web框架简单扩展思路(Salvo / Actix)
- 不要把 Connection 直接多线程共享,
Connection不是 Send。 - 方案:使用
r2d2-rusqlite连接池,每个请求从池拿连接。
Cargo.toml 添加连接池
rust
r2d2 = "0.8"
r2d2-rusqlite = "0.19"常见坑
- rusqlite Connection 不能跨线程,不要全局静态存 Connection。
- 大批量插入务必使用事务,否则性能极差。
AUTOINCREMENT只用于 INTEGER PRIMARY KEY。bundled特性会本地编译sqlite,不用系统libsqlite3,跨平台编译友好。