node-sqlite3中的事务

nkcskrwz  于 2023-05-06  发布在  Node.js
关注(0)|答案(2)|浏览(227)

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

2vuwiymt1#

我大胆地说,db.serialize()是一个不涉及任何魔法的方便方法。应该可以通过等待一个语句完成后再发送下一个语句来序列化一批语句。
这也适用于事务,唯一必须保证的事情是,在语句运行时,没有 * 其他 * 写入发生在同一个db连接对象上,以保持事务干净(如node-sqlite3 issue #304的讨论线程中所述)。
链接将通过严格调用前一个回调中的下一个语句来完成,除非前一个返回错误,此时执行应停止。
这是笨拙的,当 * 实际上 * 堆叠回调在源代码。但是如果我们promisize 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进行promisization,但这会导致在Database#run中丢失callback处理的一个细节(来自文档):
如果执行成功,this对象将包含两个名为lastIDchanges的属性,这两个属性分别包含最后插入的行ID的值和受此查询影响的行数。
我们的自定义变体捕获this对象并将其作为promise结果返回。
有了这些,我们可以定义一个经典的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));
};

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

它会记录这样的内容:

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 } ]

如果发生错误,这将导致回滚,我们将从DB引擎中返回错误消息,加上 “in statement #X”,其中X指的是批处理中的语句位置。

at0kjp5o

at0kjp5o2#

这是我使用node.js和node-sqlite3运行事务的解决方案,也许不是最优化的,但对我来说很有效。查询通过arraySql(Array of sql)参数及其参数(ArrayParams - Array of Arrays of parameters)发送。

function db_run_promise(sql, arrayParam) {
    return new Promise((resolve, reject) => {
        db.run(sql, arrayParam, (err) => {
            if (err) {
                resolve(false);
            }
            else {
                resolve(true);
            }           
        })  
    });
}

function multipleExecSQLWithTransaction(arraySqls, arrayParams) {

    let auxPromise = new Promise((resolve, reject) => {
        const myfunc = async () => {
            db.exec("BEGIN");
            let passed = true;
            for (i = 0; i < arraySqls.length; i++) {
                theSql = arraySqls[i];
                auxResult = await db_run_promise(theSql, arrayParams[i]);
                if (auxResult == false) {
                    passed = false;
                    break;
                }
            }
            if (passed == false) {
                db.exec("ROLLBACK");
                return false;
            }
            else {
                db.exec("COMMIT");
                return true;
            }
        }

        try {
            let aux = myfunc();
            resolve(aux);
        }
        catch (errT) {
            resolve(false);
        }
    });     

    return auxPromise;
}

相关问题