5

私は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
);

完全な例を実行することもできます。

問題は、またはの後の非同期一時停止中に、insertまたはクエリを使用した他のコードをupdate起動できることです。次に、この追加のクエリがトランザクションで実行されます。トランザクションがコミットされている場合、これは問題ではありません。ただし、トランザクションがロールバックされると、この余分なクエリによって行われた変更もロールバックされます。エラー メッセージが表示されずにデータが予期せず失われただけです。begininsert

私はこの問題について考えました.1つの解決策は、次のことを確認するラッパークラスを作成することだと思います:

  • 同時に実行されるトランザクションは 1 つだけです。
  • トランザクションの実行中は、トランザクションに属するクエリのみが実行されます。
  • 余分なクエリはすべてキューに入れられ、現在のトランザクションが終了した後に実行されます。
  • トランザクションが既に実行されているときにトランザクションを開始しようとすると、すべてキューに入れられます。

しかし、それは複雑すぎるソリューションのように思えます。より良いアプローチはありますか?この問題にどう対処しますか?

4

3 に答える 3

4

最初に、私は SQLite の経験がないことを述べたいと思います。私の答えは の素早い研究に基づいていnode-sqlite3ます。

コード IMHO の最大の問題は、さまざまな場所から DB に書き込もうとすることです。私が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();
    });
  });
}

関数は次のコードで呼び出されます。

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

  ...

次の変更を加えました。

  • 簡単にするために、cmdsパラメーターを追加しました。最後のパラメーターとして追加しましたが、コールバックは最後にする必要があります(cmdsは挿入された値の配列であり、最終的な実装ではSQLコマンドの配列である必要があります)
  • db.exec を db.run に変更しました (より速いはずです)
  • トランザクション内でリクエストをシリアル化するために db.serialize を追加
  • BEGIN コマンドの省略されたコールバック
  • 除外しslideていくつかunderscore

あなたのテストの実装は、私にとってはうまくいくようになりました。

于 2013-10-12T17:36:41.293 に答える
0

IMHO ivoszz の回答にはいくつかの問題があります。

  1. すべての db.run は非同期であるため、トランザクション全体の結果を確認することはできません。1 つの実行でエラーが発生した場合は、すべてのコマンドをロールバックする必要があります。これを行うには、forEach ループのコールバックで db.run("ROLLBACK") を呼び出す必要があります。db.serialize 関数は非同期実行をシリアル化しないため、「トランザクション内でトランザクションを開始できません」というエラーが発生します。
  2. forEach ループの後の "COMMIT/ROLLBACK" は、すべてのステートメントの結果をチェックする必要があり、前のすべての実行が完了する前に実行することはできません。

私見では、安全なスレッド (obv はバックグラウンド スレッド プールを参照) トランザクション管理を行う方法は 1 つしかありません。すべてのステートメントを手動でシリアル化するために、ラッパー関数を作成し、非同期ライブラリを使用します。このようにして、db.serialize 関数を回避し、(より重要な) 単一の db.run 結果をすべてチェックして、トランザクション全体をロールバックすることができます (必要に応じて promise を返します)。トランザクションに関連する node-sqlite3 ライブラリの主な問題は、1 つのエラーが発生したかどうかを確認するための serialize 関数にコールバックがないことです。

于 2016-09-12T07:10:47.460 に答える