node-sqlite3中的事务

时间:2018-11-14 11:35:56

标签: node.js node-sqlite3

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

1 个答案:

答案 0 :(得分:1)

我要弯腰说db.serialize()是一种方便的方法,不涉及任何魔术。通过等待直到一个语句完成之后再发送下一个语句,应该可以序列化一批语句。

这同样适用于事务处理,必须保证的唯一事情是,在语句中相同的db连接对象上不会发生 other 个写入正在运行,以保持事务清洁(如node-sqlite3 issue #304的讨论线程中所述)。

除非严格要求前一条的返回错误,否则必须严格调用前一条的回调中的下一条语句来进行束缚。

当通过实际上在源代码中堆叠回调完成时,这很麻烦。但是,如果我们承诺使用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进行通知,但这会导致callbackDatabase#run处理的一个细节丢失(来自the docs):

  

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

我们的自定义变体捕获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));
};

在构建承诺链时,还将构建一个语句结果数组,完成后将返回该语句结果(开头减去两个项目,第一个是undefined的{​​{1}},第二个是是Promise.resolve()的结果。

现在,我们可以轻松地在隐式事务中传递多个语句以进行序列化执行。批处理中的每个成员都可以是独立的语句,也可以是具有语句和相关参数的数组(正如BEGIN所期望的那样):

Database#run

这将记录如下内容:

SUCCESS!
[ { sql: 'DROP TABLE IF EXISTS foo;', lastID: 1, changes: 1 },
  { sql: 'CREATE TABLE foo (id INTEGER NOT NULL, name TEXT);',
    lastID: 1,
    changes: 1 },
  { sql: 'INSERT INTO foo (id, name) VALUES (?, ?);',
    lastID: 1,
    changes: 1 } ]

如果发生错误,这将导致回滚,我们将从数据库引擎中获取错误消息,外加“ in statement #X” ,其中X表示语句位置在批次中。