本地数据库为什么放主进程?better-sqlite3 怎么用?
考察点
- 原生模块和渲染进程隔离
- SQLite 放哪、怎么避免卡住 UI
参考答案
1. 为什么在主进程
better-sqlite3 是同步 API 加 .node。放渲染进程意味着:
- 要打开
nodeIntegration或在 preload 里关 sandbox,页面就能碰到数据库文件 - 同步查询卡的是页面线程
- 多窗口会各自加载一份原生模块,锁不好控
数据库文件放在 app.getPath('userData'),只由主进程打开。页面通过 invoke 要结果:
ipcMain.handle('task:list', (event, query) => {
const keyword = String(query.keyword ?? '').slice(0, 100);
// 预编译语句,不要把 keyword 拼进 SQL
return db.prepare('select id, title from task where title like ? limit 50')
.all(`%${keyword}%`);
});
2. 同步库为什么还能用
better-sqlite3 的同步调用在主进程里确实占事件循环。控制方式:
- 查询一定有
limit,列表分页 - 建索引,避免每次全表扫描
- 大批量导入放到
utilityProcess,主进程只收“导入完成” - 单条按主键读取通常是亚毫秒,可以留在主进程
不要在渲染进程用 sql.js 把整个库读进内存,除非数据小到几 MB 而且只读。
3. 多进程和打包
同一文件不要让主进程和 utility 进程一起写。要拆出去就只让一个进程持有连接。
打包时这个 .node 要 electron-rebuild,并且 asarUnpack。开发机用系统 Node 编译的二进制,放进 Electron 会报 ABI 不对。
4. 退出和损坏
before-quit 里把连接关掉。打开数据库如果发现损坏,先把文件复制备份再决定是否重建,不要直接删掉用户数据。
面试话术
本地库放 userData,连接只开在主进程,页面用 invoke 分页查询,SQL 用占位符。better-sqlite3 是同步的,所以大导入放 utilityProcess,常规按主键读取留在主进程。原生模块按 Electron ABI 重编译并 unpack。多进程不要同时写同一个文件。
常见追问
- 为什么不用 IndexedDB? 适合页面自己的缓存。要备份、要给主进程和其他窗口共用、要复杂查询时,SQLite 更合适。
- 加密数据库? 可以用 SQLCipher 一类扩展,密钥放 safeStorage。加密代替不了“页面不能直接打开文件”这道边界。