node-sqlite3 中的事务

Tir*_*esi 5 node.js node-sqlite3

在 中node-sqlite3,如果 db 当前处于序列化模式,下一条语句会在上一条语句的回调完成之前等待,还是会与下一条语句同时运行?

使用 编写事务的最佳方法是node-sqlite3什么?我已经考虑过这两种方法,但我不确定哪一种是正确的,或者即使它们都是错误的。

// NEXT DB STATEMENT WAITS FOR CALLBACK TO COMPLETE?
db.serialize(() => {

    db.run('BEGIN');

    // statement 1
    db.run(
        sql1,
        params1,
        (err) => {
            if (err) {
                console.error(err);
                return db.serialize(db.run('ROLLBACK'));
            }                           
        }
    );

    // statement 2
    db.run(
        sql2,
        params2,
        (err) => {
            if (err) {
                console.error(err);
                return db.serialize(db.run('ROLLBACK'));
            }

            return db.serialize(db.run('COMMIT));                               
        }
    );  
});



// NEXT DB STATEMENT DOES NOT WAIT FOR CALLBACK TO COMPLETE?
db.serialize(() => {

    db.run('BEGIN');

    // statement 1
    db.run(
        sql1,
        params1,
        (err) => {
            if (err) {
                console.error(err);
                return db.serialize(db.run('ROLLBACK'));
            }

            db.serialize(() => {

                // statement 2
                db.run(
                    sql2,
                    params2,
                    (err) => {
                        if (err) {
                            console.error(err);
                            return db.serialize(db.run('ROLLBACK'));
                        }

                        return db.serialize(db.run('COMMIT));                               
                    }
                );
            });                             
        }
    );
});
Run Code Online (Sandbox Code Playgroud)

Tom*_*lak 9

我要冒昧地说这db.serialize()是一种不涉及任何魔法的便捷方法。应该可以通过等待一个语句完成后再发送下一个语句来序列化一批语句。

这也适用于事务,唯一必须保证的是在运行语句时没有其他写入发生在同一个db连接对象上,以保持事务干净(如讨论线程中提到的node-sqlite3 问题# 304 )。

链接将通过严格调用前一个回调中的下一个语句来完成,除非前一个返回错误,此时应停止执行。

当通过在源代码中实际堆叠回调来完成时,这是笨拙的。但是如果我们承诺Database#run方法,我们可以使用承诺:

const sqlite3 = require('sqlite3');

sqlite3.Database.prototype.runAsync = function (sql, ...params) {
    return new Promise((resolve, reject) => {
        this.run(sql, params, function (err) {
            if (err) return reject(err);
            resolve(this);
        });
    });
};
Run Code Online (Sandbox Code Playgroud)

我们本可以依赖于util.promisify承诺,但这会导致丢失callback处理的一个细节Database#run(来自docs):

如果执行成功,该this对象将包含两个名为lastID和 的属性,changes它们分别包含最后插入的行 ID 的值和受此查询影响的行数。

我们的自定义变体捕获this对象并将其作为承诺结果返回。

有了这个,我们可以定义一个经典的承诺链,以 开始BEGIN,然后通过 链接任意数量的语句Array#reduce,最终调用COMMIT成功或ROLLBACK错误:

sqlite3.Database.prototype.runBatchAsync = function (statements) {
    var results = [];
    var batch = ['BEGIN', ...statements, 'COMMIT'];
    return batch.reduce((chain, statement) => chain.then(result => {
        results.push(result);
        return db.runAsync(...[].concat(statement));
    }), Promise.resolve())
    .catch(err => db.runAsync('ROLLBACK').then(() => Promise.reject(err +
        ' in statement #' + results.length)))
    .then(() => results.slice(2));
};
Run Code Online (Sandbox Code Playgroud)

在构建promise链时,它还构建了一个语句结果数组,完成后返回(减去开头的两个项目,第一个是undefinedfrom Promise.resolve(),第二个是 的结果BEGIN)。

现在,我们可以轻松地在隐式事务中传递多个用于序列化执行的语句。批处理的每个成员可能是一个独立的语句,也可能是一个带有语句和相关参数的数组(正如Database#run预期的那样):

var statements = [
    "DROP TABLE IF EXISTS foo;",
    "CREATE TABLE foo (id INTEGER NOT NULL, name TEXT);",
    ["INSERT INTO foo (id, name) VALUES (?, ?);", 1, "First Foo"]
];

db.runBatchAsync(statements).then(results => {
    console.log("SUCCESS!")
    console.log(results);
}).catch(err => {
    console.error("BATCH FAILED: " + err);
});
Run Code Online (Sandbox Code Playgroud)

它将记录如下内容:

成功!
[ { sql: 'DROP TABLE IF EXISTS foo;', lastID: 1, changes: 1 },
  { sql: 'CREATE TABLE foo (id INTEGER NOT NULL, name TEXT);',
    最后ID:1,
    变化:1},
  { sql: 'INSERT INTO foo (id, name) VALUES (?, ?);',
    最后ID:1,
    变化:1}]

如果出现错误,这将导致回滚,我们将从数据库引擎获取错误消息,加上“in statement #X”,其中 X 指的是批处理中的语句位置。