Node.js SQLite statement has been finalized 错误:原因与修复

Error: statement has been finalized
    at readDeviceStates (src/lib/database.ts:76:38)
    { code: 'ERR_INVALID_STATE' }

这个错误来自 Node.js 22+ 的内置 node:sqlite 模块,对已经调用了 finalize()StatementSync 对象再次操作时抛出。

原因一:全局缓存 Statement 被意外 finalize

// 错误写法:模块级共享 statement
const readStmt = db.prepare("SELECT * FROM device_states");

export function readDeviceStates() {
    return readStmt.all();  // 若 readStmt 被 finalize 则崩溃
}

如果在某处调用了 readStmt.finalize(),后续所有使用都会报错。

原因二:using 关键字自动释放

Node.js SQLite 支持 using 声明(Explicit Resource Management),离开作用域后自动调用 finalize()

// 错误:返回迭代器后 stmt 已被 finalize
function getRows() {
    using stmt = db.prepare("SELECT * FROM logs");
    return stmt.iterate();  // 离开函数后 stmt 自动 finalize,迭代器失效
}

修复:每次使用时重新 prepare

// 正确写法:每次都 prepare,不缓存
export function readDeviceStates() {
    const stmt = db.prepare("SELECT * FROM device_states");
    const result = stmt.all();
    stmt.finalize();
    return result;
}

或者更简洁,不手动调用 finalize(Node.js 会在 GC 时自动释放):

export function readDeviceStates() {
    return db.prepare("SELECT * FROM device_states").all();
}

需要性能优化时:用对象包装

如果 prepare 开销较大,可以在模块初始化时准备,但确保 finalize 只在明确不再使用时调用:

class DeviceRepository {
    private readStmt: StatementSync;

    constructor(private db: DatabaseSync) {
        this.readStmt = db.prepare("SELECT * FROM device_states");
    }

    getAll() {
        return this.readStmt.all();
    }

    close() {
        this.readStmt.finalize();
        this.db.close();
    }
}

生命周期由对象管理,close() 明确释放资源。

ExperimentalWarning 的处理

(node:2436) ExperimentalWarning: SQLite is an experimental feature

node:sqlite 在 Node.js 22 中是实验性功能,可以用以下方式消除警告:

node --no-experimental-warnings server.js

或在代码中:

process.removeAllListeners('warning');

生产环境建议关注 Node.js 版本更新,等待 SQLite 模块稳定。