Node.js + SQLite 异步事务

Str*_*rix 5 sqlite asynchronous transactions node.js

我正在使用node-sqlite3,但我确信这个问题也出现在另一个数据库库中。我在混合事务和异步代码的代码中发现了一个错误。

function insertData(arrayWithData, callback) {
    // start a transaction
    db.run("BEGIN", function() {
        // do multiple inserts
        slide.asyncMap(
            arrayWithData,
            function(cb) {
                db.run("INSERT ...", cb);
            },
            function() {
                // all done
                db.run("COMMIT");
            }
        );
    });
}

// some other insert
setInterval(
    function() { db.run("INSERT ...", cb); },
    100
);
Run Code Online (Sandbox Code Playgroud)

您还可以运行完整的示例

问题是,在or之后的异步暂停期间,可以启动带有insert或查询的其他一些代码。然后这个额外的查询在事务中运行。当事务提交时这不是问题。但是,如果事务回滚,则此额外查询所做的更改也会回滚。哎呀,我们刚刚意外地丢失了数据,但没有任何错误消息。updatebegininsert

我考虑过这个问题,我认为一个解决方案是创建一个包装类,以确保:

  • 同一时间只有一项事务正在运行。
  • 当事务运行时,仅执行属于该事务的查询。
  • 所有额外的查询都会在当前事务完成后排队并执行。
  • 当事务已在运行时启动事务的所有尝试也将排队。

但这听起来像是太复杂的解决方案。有更好的方法吗?你如何处理这个问题?

ivo*_*szz 4

首先,我想声明我没有使用 SQLite 的经验。我的回答是基于对 的快速研究node-sqlite3

恕我直言,您的代码最大的问题是您尝试从不同的位置写入数据库。据我了解 SQLite,您无法像在 PostgreSQL 中那样控制不同的并行“连接”,因此您可能需要包装与 DB 的所有通信。我修改了您的示例以使用始终insertData包装器。这是修改后的函数:

function insertData(callback, cmds) {
  // start a transaction
  db.serialize(function() {
    db.run("BEGIN;");
    //console.log('insertData -> begin');
    // do multiple inserts
    cmds.forEach(function(item) {
      db.run("INSERT INTO data (t) VALUES (?)", item, function(e) {
        if (e) {
          console.log('error');
          // rollback here
        } else {
          //console.log(item);
        }
      });
    });
    // all done
    //here should be commit
    //console.log('insertData -> commit');
    db.run("ROLLBACK;", function(e) {
      return callback();
    });
  });
}
Run Code Online (Sandbox Code Playgroud)

使用以下代码调用函数:

init(function() {
  // insert with transaction
  function doTransactionInsert(e) {
    if (e) return console.log(e);
    setTimeout(insertData, 10, doTransactionInsert, ['all', 'your', 'base', 'are', 'belong', 'to', 'us']);
  }

  doTransactionInsert();

  // Insert increasing integers 0, 1, 2, ...
  var i=0;

  function doIntegerInsert() {
    //console.log('integer insert');
    insertData(function(e) {
      if (e) return console.log(e);
      setTimeout(doIntegerInsert, 9);
    }, [i++]);
  }

  ...
Run Code Online (Sandbox Code Playgroud)

我做了以下更改:

  • 添加了 cmds 参数,为了简单起见,我将其添加为最后一个参数,但回调应该是最后一个(cmds 是插入值的数组,在最终实现中它应该是 SQL 命令的数组)
  • 将 db.exec 更改为 db.run (应该更快)
  • 添加 db.serialize 以序列化事务内的请求
  • BEGIN 命令的提交回调
  • 留下slide一些underscore

您的测试实现现在对我来说效果很好。