数据库:SQL 插件与 SQLite 实战
本地存储是桌面应用的刚需:用户数据、缓存、历史记录总要落盘。Tauri 官方提供 tauri-plugin-sql,用 Rust 的 sqlx 驱动 SQLite 与 MySQL,前端可以直接执行 SQL。本篇讲插件接入、连接管理、迁移与分层架构的最佳实践。
一、插件安装与初始化
先在 Rust 端添加插件依赖,并在 Builder 注册:
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_sql::Builder::default().build())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
前端安装对应 npm 包:
npm install @tauri-apps/plugin-sql
前端即可直接打开连接并执行 SQL,连接由 Rust 端持久持有:
import Database from '@tauri-apps/plugin-sql'
const db = await Database.load('sqlite:app.db')
await db.execute(
'CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, title TEXT, updated INTEGER)'
)
await db.execute('INSERT INTO notes (title, updated) VALUES ($1, $2)', ['第一条', Date.now()])
const rows = await db.select('SELECT * FROM notes ORDER BY updated DESC')
注意 SQL 语句的写法以插件文档为准,不同后端的占位符风格可能不同。表结构变更建议走迁移机制,具体见第三节。
二、架构选择:前端直连还是 Rust 中转
tauri-plugin-sql 允许前端直接执行 SQL,但这不代表应该把所有 SQL 散在组件里。两种架构对比:
| 架构 | 说明 | 适用 |
|---|---|---|
| 前端直连 | 组件里写 SQL | 原型、个人小工具 |
| Rust 中转 | SQL 集中在 Rust 命令,前端只调命令 | 中大型应用 |
生产建议用 Rust 中转:类型安全、可加业务校验、SQL 不暴露给前端。Rust 端直接用 sqlx 与连接池:
use sqlx::sqlite::SqlitePool;
pub struct AppState {
pub pool: SqlitePool,
}
#[tauri::command]
async fn list_notes(state: tauri::State<'_, AppState>) -> Result<Vec<Note>, String> {
sqlx::query_as::<_, Note>("SELECT id, title FROM notes")
.fetch_all(&state.pool)
.await
.map_err(|e| e.to_string())
}
连接池放 State,随应用生命周期管理,写法见「全局状态:tauri::State 与共享数据」。
三、迁移管理
表结构总会演进。Rust 端用 sqlx 的 migrate 宏管理迁移:
async fn run_migrations(pool: &SqlitePool) -> Result<(), String> {
sqlx::migrate!("./migrations")
.run(pool)
.await
.map_err(|e| e.to_string())
}
迁移文件按版本编号,形如 0001_init.sql、0002_add_tags.sql。也可以在插件配置中声明,自动执行 migration,具体以插件文档为准。
| 迁移实践 | 说明 |
|---|---|
| 只增不改 | 历史迁移文件不可修改 |
| 逐步演进 | 每次变更一个文件 |
| 备份优先 | 升级前提醒用户导出数据 |
| 测试覆盖 | 新老版本都要跑通 |
四、插件配置与数据库位置
数据库文件放在应用数据目录,路径通过 app.path() 取得:
use tauri::Manager;
fn main() {
tauri::Builder::default()
.setup(|app| {
let app_data = app.path().app_data_dir().unwrap();
println!("数据目录:{}", app_data.display());
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
访问数据目录对应的路径需要在 Capability 中配置 fs 作用域,权限细节见「权限模型:Capability 与作用域」。打包发布后,数据目录也是自动更新时必须保留的位置,跨版本兼容见「自动更新与版本分发」。
五、性能与维护
SQLite 单文件数据库足够支撑绝大多数桌面应用,但要注意几条:批量写入用事务,比逐条执行快一个量级;大结果集分页查询,别一次全取;定期 VACUUM 优化文件体积;索引按查询条件建。
| 场景 | 优化建议 |
|---|---|
| 批量插入 | 显式事务包裹 |
| 列表展示 | LIMIT 分页 |
| 模糊搜索 | LIKE 前缀或 FTS |
| 导入导出 | 流式处理避免 OOM |
数据库相关的 Rust 异步与并发知识见「异步命令与后台任务」与「所有权与线程安全:Send、Sync 与 Mutex」。写 SQL 与迁移文件时,用 Claude Code 指南 的 AI 辅助方式能显著提效;分析慢查询日志时 正则 mastery 是利器;Rust 结构体与 derive 的基础见 Rust 入门。
六、数据模型设计实战
SQLite 的灵活性容易让人跳过设计直接建表,业务一复杂就返工。几条经验:
| 原则 | 说明 |
|---|---|
| 唯一约束先行 | 该唯一的字段立刻加 UNIQUE |
| 外键显式声明 | 开启 PRAGMA foreign_keys |
| 时间用 INTEGER | 存时间戳,展示时格式化 |
| 大字段单独表 | 正文与元数据分表 |
| 预留扩展字段 | 常用 JSON 列放动态字段 |
SQLite 默认不开启外键约束 SQLite 默认不开启外键约束,需要每次连接执行 PRAGMA foreign_keys = ON,否则级联删除不生效,这是最常被忽略的细节。
另一个高频问题是并发写:SQLite 有写锁,多个后台任务同时写会偶发锁冲突。解法是所有写操作走同一个连接或连接池的单写模式,配合 WAL 模式提升并发性能,具体参数以 SQLite 官方文档为准。
SQLite 在 Tauri 应用里几乎是默认选择:零服务器、单文件、事务可靠,配合 sqlx 的编译期 SQL 校验更是如虎添翼。数据库相关的具体 API 以插件与 sqlx 文档为准,遇到迁移问题优先回到官方示例核对。
最后一点经验:数据库迁移文件与业务代码同等重要,同样要 review、同样要测试。很多团队只看应用代码不看迁移,直到某次升级把用户数据加锁卡死。把迁移当作一次正式的功能变更对待,数据库就能安静地支撑应用跑很久。
数据库部分收束成四句话:选 SQLite、走 Rust 中转、迁移只增不改、写操作走事务。记住这四条就能稳稳支撑大部分桌面应用。性能调优等数据量真起来再做也不迟,过早优化在数据库上同样成立。
延伸阅读
- 全局状态:tauri::State 与共享数据——连接池的标准存放位置
- 异步命令与后台任务——异步查询的写法
- Rust 入门:结构体与错误处理前置知识
