5

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

使用 编写事务的最佳方法是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));                               
                    }
                );
            });                             
        }
    );
});
4

1 回答 1

11

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

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

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

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

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);
        });
    });
};

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

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

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

有了这些,我们可以定义一个经典的 Promise 链,从 开始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));
};

由于这构建了承诺链,它还构建了一个语句结果数组,它在完成时返回(在开始时减去两个项目,第一个是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);
});

这将记录如下内容:

成功!
[ { 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 指的是批处理中的语句位置。

于 2018-11-15T14:47:00.207 回答