Strix Strix - 11 months ago 144
Node.js Question

Node.js + SQLite async transactions

I am using node-sqlite3, but I am sure this problem appears in another database libraries too. I have discovered a bug in my code with mixing transactions and async code.

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

// some other insert
function() {"INSERT ...", cb); },

You can also run the full example.

The problem is that some other code with
query can be launched during the async pause after
. Then this extra query is run in the transaction. This is not a problem when the transaction is committed. But if the transaction is rolled back the change made by this extra query is also rolled back. Hoops we've just unpredictably lost data without any error message.

I thought about this issue and I think that one solution is to create a wrapper class that will make sure that:

  • Only one transaction is running at the same time.

  • When transaction is running only queries which belong to the transaction are executed.

  • All the extra queries are queued and executed after the current transaction is finished.

  • All attempts to start a transaction when one is already running will also get queued.

But it sounds like too complicated solution. Is there a better approach? How do you deal with this problem?

Answer Source

At first, I would like to state that I have no experience with SQLite. My answer is based on quick study of node-sqlite3.

The biggest problem with your code IMHO is that you try to write to DB from different locations. As I understand SQLite, you have no control of different parallel "connections" as you have in PostgreSQL, so you probably need to wrap all your communication with DB. I modified your example to use always insertData wrapper. Here is the modified function:

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

Function is called with this code:

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']);


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


I made following changes:

  • added cmds parameter, for simplicity I added it as last parameter but callback should be last (cmds is an array of inserted values, in final implementation it should be an array of SQL commands)
  • changed db.exec to (should be quicker)
  • added db.serialize to serialize requests inside transaction
  • ommited callback for BEGIN command
  • leave out slide and some underscore

Your test implementation now works fine for me.