Skip to content

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. 参数绑定 ?1 ?2 千万不要字符串拼接SQL,使用 params![],防止SQL注入。
  2. NULL 处理Option<i32> 对应数据库 NULL,数据库可为空字段一律用 Option。
  3. 内存数据库Connection::open(":memory:"),程序结束数据消失,单元测试很方便。
  4. 事务示例
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"

常见坑

  1. rusqlite Connection 不能跨线程,不要全局静态存 Connection。
  2. 大批量插入务必使用事务,否则性能极差。
  3. AUTOINCREMENT 只用于 INTEGER PRIMARY KEY。
  4. bundled 特性会本地编译sqlite,不用系统libsqlite3,跨平台编译友好。