在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));
}
);
});
}
);
});
答案 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
进行通知,但这会导致callback
中Database#run
处理的一个细节丢失(来自the docs):
如果执行成功,则
this
对象将包含两个名为lastID
和changes
的属性,这两个属性包含最后插入的行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表示语句位置在批次中。