Транзакции в узле-sqlite3 - PullRequest
       7

Транзакции в узле-sqlite3

0 голосов
/ 14 ноября 2018

В 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));                               
                    }
                );
            });                             
        }
    );
});

1 Ответ

0 голосов
/ 15 ноября 2018

Я выхожу на конечность и говорю, что db.serialize() - это удобный метод, который не включает никакой магии. Должна быть возможность сериализации пакета операторов, ожидая, пока одно не завершится, перед отправкой следующего.

Это также будет работать для транзакций, единственное, что должно быть гарантировано , это то, что никакие другие записи не происходят с тем же объектом соединения db во время выполнения операторов, поддерживать чистоту транзакции (как отмечалось в ветке обсуждения для node-sqlite3, выпуск # 304 ).

Цепочка будет выполняться путем строгого вызова следующего оператора в обратном вызове предыдущего, если только предыдущий не вернул ошибку, после чего выполнение должно быть остановлено.

Это неудобно, когда фактически укладывает обратные вызовы в исходном коде. Но если мы обещаем метод Database#run, мы можем использовать обещания:

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 (из документов * 1027) *):

Если выполнение было успешным, объект this будет содержать два свойства с именами lastID и changes, которые содержат значение идентификатора последней вставленной строки и количество строк, затронутых этим запросом соответственно.

Наш пользовательский вариант захватывает объект this и возвращает его в качестве результата обещания.

Таким образом, мы можем определить классическую цепочку обещаний, начиная с 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 из Promise.resolve(), второй - результат * * тысяча сорок-девять).

Теперь мы можем легко передать несколько операторов для сериализованного выполнения внутри неявной транзакции. Каждый член пакета может быть либо отдельным оператором, либо массивом с оператором и связанными параметрами (как ожидал бы 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 } ]

В случае ошибки это приведет к откату, и мы получим сообщение об ошибке от механизма БД, плюс "в операторе #X" , где X относится к позиции оператора в партия.

...