一、为什么需要跨平台本地数据库

如果你用Electron开发桌面应用,肯定会遇到数据存储的问题。虽然可以用远程数据库,但很多场景下(比如离线应用、单机工具),本地存储才是刚需。更麻烦的是,Windows/macOS/Linux这三个系统的文件路径、权限机制完全不同,直接操作文件很容易踩坑。

这时候就需要一个能跨平台的本地数据库方案,它需要满足:

  1. 打包后体积小,别让安装包膨胀
  2. 读写速度快,不能卡界面
  3. 支持复杂查询,不能只当键值存储用
  4. 最重要的是——在不同系统上表现一致

二、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);

七、安全注意事项

  1. 永远不要用字符串拼接SQL:
// 错误!会被SQL注入
const badQuery = `SELECT * FROM users WHERE name = '${userInput}'`;

// 正确做法:用预处理语句
const goodQuery = db.prepare('SELECT * FROM users WHERE name = ?');
goodQuery.get(userInput);
  1. 敏感数据加密:
// 使用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以下)

最后记住两个黄金法则:

  1. 永远使用预处理语句防注入
  2. 事务操作保证数据一致性