You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Electron集成better-sqlite3-multiple-ciphers报SQLITE_NOTADB错误排查

问题描述

我参考《Sqlite Error: file is not a database》中的指引,二次确认数据库路径未包含.db扩展名,相关数据库路径获取、表初始化、数据插入逻辑代码如下:

export const getInfopiecesDbpath = () {
  const isEnvDevelopment = process.env.NODE_ENV === 'development'

  const infopiecesDbpath = isEnvDevelopment
    ? path.join(app.getAppPath(), "src", "data", "infopieces")
    : path.join(app.getAppPath(), ".webpack", "data", "infopieces")
  return infopiecesDbpath
}

const createInfopiecesTable: any = `
  CREATE TABLE IF NOT EXISTS infopieces (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    infotext TEXT NOT NULL
  );`

const createInfopiecesIndex = `PRAGMA INDEX_LIST(infopieces);`
 
export function initializeInfopiecesDB(db: Database) {
  db.prepare(createInfopiecesTable).run()
  db.prepare(createInfopiecesIndex).get()
}

const insertIP = `
    INSERT INTO infopiecesDb (id, infotext)
    VALUES (ip.id, ip.infotext)
  `

export const insertInfopiece = (ip: IInfopiece) => {
  infopiecesDb.prepare(insertIP).run(ip)
}

let infopiecesDbpath = getInfopiecesDbpath()
let infopiecesDb = require('better-sqlite3-multiple-ciphers')(infopiecesDbpath, { verbose: console.log })
infopiecesDb.pragma("key='secret-key'")

initializeInfopiecesDB(infopiecesDb)

main主进程中的实现代码如下:

let infopiecesDbpath = getInfopiecesDbpath()
let infopiecesDb = require('better-sqlite3-multiple-ciphers')(infopiecesDbpath, { verbose: console.log })
infopiecesDb.pragma("key='secret-key'")
initializeInfopiecesDB(infopiecesDb)


ipcMain.handle ("insert-infopiece-intodb", (IpcMainEvent, message) => {
  console.log("ipcMain.hanlde-insert-infopiece-intodb-message: ", message.ip_01)
  insertInfopiece(message.ip_01)
})

运行触发插入逻辑时,抛出如下错误:

Error occurred in handler for 'insert-infopiece-intodb': SqliteError: file is not a database
    at Database.prepare (/home/raphy/Playground/.webpack/main/index.js:652:21)
    at insertInfopiece (/home/raphy/Playground/.webpack/main/index.js:1773:18)
    at /home/raphy/Playground/.webpack/main/index.js:1943:32
    at node:electron/js2c/browser_init:193:579
    at EventEmitter.<anonymous> (node:electron/js2c/browser_init:161:10433)
    at EventEmitter.emit (node:events:390:28) {
  code: 'SQLITE_NOTADB'
}

项目相关依赖及运行环境版本信息如下:

"@types/better-sqlite3": "^7.5.0",
"better-sqlite3": "^7.5.3",
"better-sqlite3-multiple-ciphers": "^7.5.2",
"electron": "17"
node:  v16.15.0

目前不清楚哪部分配置或代码实现存在问题。

更新测试1

若在main进程中写入如下逻辑:

const infopiecesDb = require('better-sqlite3-multiple-ciphers')("../data/infopieces.sqlite", {timeout: 5000})
infopiecesDb.exec(`CREATE TABLE IF NOT EXISTS infopieces (
id INTEGER PRIMARY KEY AUTOINCREMENT,
infotext TEXT NOT NULL,
doctype VARCHAR(255),
dateStamp DATETIME NOT NULL,
shelfLife DATETIME NOT NULL,
source VARCHAR(255) NOT NULL,
lineage TEXT,
reliability INTEGER,
person VARCHAR(255),
company VARCHAR(255),
privacy INTEGER NOT NULL,
authorization TEXT,
tag VARCHAR(255)
);`)

infopiecesDb.pragma("key='secret-key'")

会抛出SqliteError: unable to open database file错误,寻求问题的根因及修复方案。


问题根因

一共4处核心错误:

  • SQL语句表名错误:插入语句insertIP里写的表名是infopiecesDb,实际创建的表名是infopieces,infopiecesDb是代码里的数据库连接变量名,不是表名。
  • 加密密钥设置顺序完全颠倒:better-sqlite3-multiple-ciphers要求创建数据库实例后,必须第一时间执行密钥设置的pragma,再执行任何建表、读写操作。测试1代码里先执行建表操作、后设置密钥,相当于先创建了明文数据库文件,之后强行用加密模式读取,必然触发file is not a database错误。
  • 数据库存储路径无写入权限/路径错误:
    • 生产环境路径拼接用了app.getAppPath()下的.webpack/data目录,Electron打包后该目录位于只读的app.asar包内,没有写入权限,无法创建、修改数据库文件。
    • 测试1里用的相对路径../data/infopieces.sqlite是相对于Electron进程启动的工作目录,不是源码所在目录,开发、生产环境下工作目录不固定,大概率指向不存在或无权限的位置,触发unable to open database file错误。
  • 重复初始化数据库实例:数据库工具模块里已经创建了一次infopiecesDb实例,主进程启动时又重复创建了一次同路径的实例,容易出现实例加密状态不一致、文件锁冲突的问题。
修复方案

按以下步骤逐一修改:

  • 修正数据库存储路径:用Electron提供的app.getPath('userData')作为数据库存储根目录,该目录是系统专门给应用分配的可读写用户数据目录,不会出现权限问题,开发、生产环境路径稳定。修改getInfopiecesDbpath函数:
import { app } from 'electron'
import path from 'path'
import fs from 'fs'

export const getInfopiecesDbpath = () {
  // 用userData目录作为存储根,确保有写入权限
  const dbRoot = path.join(app.getPath('userData'), 'data')
  // 目录不存在则提前创建
  if (!fs.existsSync(dbRoot)) {
    fs.mkdirSync(dbRoot, { recursive: true })
  }
  // 可以加.sqlite后缀方便识别,不影响加密逻辑
  return path.join(dbRoot, "infopieces.sqlite")
}
  • 调整加密操作顺序,修正SQL语句错误:
    • 所有数据库实例创建后,第一时间设置密钥,再做表初始化、读写操作。
    • 修正插入语句的表名错误:
// 修正表名,把infopiecesDb改成实际表名infopieces
const insertIP = `
    INSERT INTO infopieces (id, infotext)
    VALUES (@id, @infotext)
  `
  • 删除重复的数据库实例初始化逻辑:只保留一份数据库实例初始化代码,统一在主进程启动时初始化一次,其他模块复用该实例,不要重复创建连接。正确的初始化顺序:
// 主进程入口,只初始化一次
import { initializeInfopiecesDB, insertInfopiece, getInfopiecesDbpath } from './db'
import Database from 'better-sqlite3-multiple-ciphers'

// 获取正确的可读写路径
const infopiecesDbpath = getInfopiecesDbpath()
// 创建数据库实例
const infopiecesDb = new Database(infopiecesDbpath, { verbose: console.log })
// 第一时间设置加密密钥,这一步必须在所有数据库操作之前
infopiecesDb.pragma("key='secret-key'")
// 密钥设置完成后,再初始化表结构
initializeInfopiecesDB(infopiecesDb)

// IPC逻辑复用已经初始化好的实例即可,不要重复创建连接
ipcMain.handle ("insert-infopiece-intodb", (_, message) => {
  console.log("ipcMain.hanlde-insert-infopiece-intodb-message: ", message.ip_01)
  insertInfopiece(message.ip_01)
})
  • 清理旧的错误数据库文件:之前代码生成的错误的明文/半加密文件会干扰测试,先把对应路径下生成的infopieces、infopieces.sqlite旧文件删掉,再重新启动应用测试即可。

内容的提问来源于stack exchange,提问作者Raphael10

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 14:15:37