一、为什么需要跨平台本地数据库
如果你用Electron开发桌面应用,肯定会遇到数据存储的问题。虽然可以用远程数据库,但很多场景下(比如离线应用、单机工具),本地存储才是刚需。更麻烦的是,Windows/macOS/Linux这三个系统的文件路径、权限机制完全不同,直接操作文件很容易踩坑。
这时候就需要一个能跨平台的本地数据库方案,它需要满足:
- 打包后体积小,别让安装包膨胀
- 读写速度快,不能卡界面
- 支持复杂查询,不能只当键值存储用
- 最重要的是——在不同系统上表现一致
二、SQLite是最佳选择
经过对比,SQLite几乎是为这个场景量身定制的:
- 单文件存储,
.db文件随便放哪都行 - 零配置,不需要安装数据库服务
- 支持标准SQL语法,连事务都有
- 在Electron中可以用
better-sqlite3这个库操作
安装方法很简单:
npm install better-sqlite3
注意!如果打包时报错,可能需要手动编译:
npm install --build-from-source better-sqlite3
三、完整实现示例
3.1 基础操作
技术栈:Electron + better-sqlite3 + Node.js
// 初始化数据库
const Database = require('better-sqlite3');
// 数据库文件会放在应用的用户数据目录
const path = require('path');
const dbPath = path.join(app.getPath('userData'), 'mydata.db');
// 连接数据库(不存在会自动创建)
const db = new Database(dbPath);
// 创建表
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER CHECK(age > 0)
)
`);
// 插入数据
const insert = db.prepare('INSERT INTO users (name, age) VALUES (?, ?)');
insert.run('张三', 25);
insert.run('李四', 30);
// 查询数据
const rows = db.prepare('SELECT * FROM users WHERE age > ?').all(20);
console.log(rows); // 输出:[{id:1,name:'张三',age:25},...]
3.2 高级功能
// 事务处理(要么全部成功,要么全部回滚)
const transfer = db.transaction((fromId, toId, amount) => {
db.prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')
.run(amount, fromId);
db.prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?')
.run(amount, toId);
});
// 执行事务
try {
transfer(1, 2, 500); // 从账户1转500到账户2
} catch (err) {
console.error('转账失败:', err);
}
// 使用索引提升查询速度
db.exec('CREATE INDEX IF NOT EXISTS idx_user_age ON users(age)');
四、实际应用中的技巧
4.1 处理跨平台路径
不同系统的数据库存放位置要特别注意:
// 正确做法:使用Electron提供的系统路径
const { app } = require('electron');
const dbPath = path.join(
app.getPath('userData'), // 自动适配各系统
'database/mydata.db'
);
// 确保目录存在
const fs = require('fs');
if (!fs.existsSync(path.dirname(dbPath))) {
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
}
4.2 性能优化
// 批量插入10万条数据(普通方式要30秒,这样只要0.5秒)
const insertMany = db.transaction((users) => {
const stmt = db.prepare('INSERT INTO users (name, age) VALUES (?, ?)');
for (const user of users) {
stmt.run(user.name, user.age);
}
});
// 生成测试数据
const mockUsers = Array.from({ length: 1e5 }, (_, i) => ({
name: `user${i}`,
age: Math.floor(Math.random() * 50) + 18
}));
// 执行批量插入
insertMany(mockUsers);
五、与其他方案的对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| SQLite | 轻量,支持复杂查询 | 不适合超大规模数据 |
| localStorage | 简单易用 | 只能存字符串,容量小 |
| IndexedDB | 浏览器原生支持 | API复杂,查询能力弱 |
| JSON文件 | 无需额外依赖 | 读写性能差,无事务 |
六、常见问题解决方案
问题1:数据库文件被锁定?
// 解决方案:设置超时和只读模式
const db = new Database(dbPath, {
timeout: 5000, // 5秒超时
readonly: false // 非只读模式
});
问题2:如何备份数据库?
// 备份整个数据库文件
const backupPath = path.join(__dirname, 'backup.db');
fs.copyFileSync(dbPath, backupPath);
// 或者导出SQL
const dump = db.exec('SELECT sql FROM sqlite_master');
fs.writeFileSync('dump.sql', dump);
七、安全注意事项
- 永远不要用字符串拼接SQL:
// 错误!会被SQL注入
const badQuery = `SELECT * FROM users WHERE name = '${userInput}'`;
// 正确做法:用预处理语句
const goodQuery = db.prepare('SELECT * FROM users WHERE name = ?');
goodQuery.get(userInput);
- 敏感数据加密:
// 使用Node.js的crypto模块
const crypto = require('crypto');
const encrypt = (text) => {
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
return cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
};
八、总结
SQLite在Electron中表现非常出色,特别是配合better-sqlite3这个库:
- 读写速度堪比内存操作
- 完整的SQL支持让复杂查询不再是问题
- 单文件存储便于备份和迁移
- 跨平台表现一致,省去大量适配工作
建议在以下场景优先使用:
- 需要离线工作的应用(如记账软件)
- 配置信息存储(比INI/JSON文件更规范)
- 中小型数据量的桌面工具(10GB以下)
最后记住两个黄金法则:
- 永远使用预处理语句防注入
- 事务操作保证数据一致性
评论