人人都会AI编程

SQLite 集成:本地关系型数据库

更新时间:2026-07-11

在桌面应用中,很多场景需要持久化数据——用户配置、本地缓存、离线数据仓库等。相比于简单的 JSON 文件或 localStorage,关系型数据库在处理复杂查询、数据一致性和并发读写时有不可替代的优势。Tauri 可以直接集成 SQLite,通过 Rust 后端操作数据库,再通过 IPC 把结果传给前端,整个过程高效、安全,且不会增加额外的运行时依赖。


为什么选择 SQLite?

  • 零配置,零服务:SQLite 是嵌入式数据库,不需要单独安装或启动服务,数据库就是一个文件,随应用一起分发。
  • 体积极小:SQLite 库本身只有几百 KB,编译进 Tauri 的 Rust 内核后基本不增加包体积。
  • 完整 SQL 支持:支持大多数标准 SQL 语法、事务、索引、触发器等,甚至支持 JSON 查询。
  • 跨平台一致:文件格式跨平台完全兼容,Windows 上的数据库文件可以直接拷贝到 macOS 或 Linux 使用。
  • 并发处理:SQLite 支持多线程读,单线程写,在桌面应用场景下完全够用,且避免了客户端/服务器数据库的维护成本。

集成方式

Tauri 不强制特定数据库库,但社区最常用的方案是通过 Rust 的 rusqlite 库操作 SQLite。下面是一个最小化示例,展示如何初始化数据库、创建表、插入数据并把结果返回给前端。

1. 添加依赖

src-tauri/Cargo.toml 中加入:

[dependencies]
rusqlite = { version = "0.31", features = ["bundled"] }

其中 features = ["bundled"] 会将 SQLite 源码编译进应用,避免依赖系统库。

2. 初始化数据库连接

在应用启动时建立连接,通常放在 Tauri 的 setup 钩子中,并通过 Tauri 的状态管理让各命令共享连接。

use tauri::Manager;
use rusqlite::Connection;
use std::sync::Mutex;

struct DbState(Mutex<Connection>);

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let conn = Connection::open(app.path().app_data_dir()?.join("mydb.sqlite"))?;
            conn.execute(
                "CREATE TABLE IF NOT EXISTS notes (
                    id INTEGER PRIMARY KEY,
                    title TEXT NOT NULL,
                    content TEXT,
                    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
                )",
                [],
            )?;
            app.manage(DbState(Mutex::new(conn)));
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![add_note, get_notes])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

3. 定义命令

前端通过 Tauri 命令调用这些函数:

#[tauri::command]
fn add_note(state: tauri::State<DbState>, title: String, content: String) -> Result<(), String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    conn.execute("INSERT INTO notes (title, content) VALUES (?1, ?2)", &[&title, &content])
        .map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn get_notes(state: tauri::State<DbState>) -> Result<Vec<Note>, String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    let mut stmt = conn.prepare("SELECT id, title, content, created_at FROM notes ORDER BY created_at DESC")
        .map_err(|e| e.to_string())?;
    let notes = stmt.query_map([], |row| {
        Ok(Note {
            id: row.get(0)?,
            title: row.get(1)?,
            content: row.get(2)?,
            created_at: row.get(3)?,
        })
    }).map_err(|e| e.to_string())?
    .collect::<Result<Vec<_>, _>>()
    .map_err(|e| e.to_string())?;
    Ok(notes)
}

#[derive(serde::Serialize)]
struct Note {
    id: i32,
    title: String,
    content: String,
    created_at: String,
}

4. 前端调用

import { invoke } from '@tauri-apps/api/tauri';

async function saveNote() {
  await invoke('add_note', { title: '新笔记', content: '内容...' });
  const notes = await invoke('get_notes');
  console.log(notes);
}

注意事项

  • 并发安全rusqlite::Connection 不是 Send 的,所以需要用 Mutex 包裹。对于高频读写的场景,可以使用连接池(如 r2d2-sqlite)或启用 WAL 模式提高并发。
  • 数据库文件位置:推荐使用 app.path().app_data_dir() 获取平台对应的数据目录,确保数据库文件不会因应用更新被覆盖或权限错误。
  • 迁移管理:项目变大后建议引入轻量的 schema 迁移工具(如 refinery 或手动维护版本表),避免直接改表导致用户数据丢失。
  • 安全性:所有数据库操作都在 Rust 端执行,前端永远无法直接访问数据库文件,这比在 WebView 中操作 IndexedDB 或 localStorage 更安全,也有利于防止 SQL 注入(参数化查询已用 ?1, ?2 处理)。

实际应用场景

  • 记事本 / 笔记应用:存储用户笔记、标签、附件路径。
  • 本地缓存:将服务端数据镜像到本地,减少网络请求并支持离线查看。
  • 日志分析工具:导入日志文件后存入 SQLite,利用 SQL 快速查询和聚合。
  • 个人财务软件:管理收支记录、分类统计,支持复杂报表。
  • 桌面端小游戏:存储分数、关卡进度等。

相比直接用 JSON 文件,SQLite 能让你在处理数千条数据时依然保持查询速度和数据完整性,而不会像文本文件那样出现读写冲突或性能急剧下降。


一句话总结:Tauri + SQLite 的组合把“桌面应用本地数据库”这件事做到了极致——零配置、高性能、跨平台一致,你只需专注业务逻辑,剩下的交给 Rust 和 SQLite 处理。