1

Consider the code below that gets some data from an external service and inserts it into some table in a database. It starts the transactions and commits it when all the data is inserted or rolls it back if an error occurres:

const mysql = require('mysql2/promise');

class SomeClass
{
    async connect(params)
    {
        this.db = await mysql.createConnection(params);
    }

    async insertSomeRowsIntoSomeTable()
    {
        await this.db.execute('START TRANSACTION;');

        try {
            for (let i = 0; i < 100; i++) {
                //get the values to insert from an external service, for example.
                await const values = service.getValues();
                await this.db.execute('INSERT SomeTable .... <the values>...');
            }
            await this.db.execute('COMMIT;');
        } 
        catch (e) {
            await this.db.execute('ROLLBACK;');
        }
    }
}

but what if there is another async method updateSomething that updates a row in some table with a single update query and the client calls it while insertSomeRowsIntoSomeTable method is still running? Obviously, the updated data will be lost if the an error occurres in insertSomeRowsIntoSomeTable.

If there is another method that initiates its own transaction, for example, we unexpectedly get the nested transaction.

The only idea I have at the moment is to make all the operations with the database atomic by moving all the transaction logic to the stored procedures, for example, but it look like a workaround but not a solution.

Having a separate database connection in each async method is not an option, for the obvious reasons.

Does anyone have a better idea?

2
  • take a look stackoverflow.com/questions/50185816/… Commented Oct 30, 2019 at 22:07
  • Don't use a single shared connection (what you called db) for all methods of your instance. Use on connection per transaction/method, and pool them. Commented Dec 3, 2019 at 23:47

1 Answer 1

3

Probably the easiest solution is to use pool:

const mysql = require('mysql2/promise');

class SomeClass
{
    async connect(params)
    {
        this.db = mysql.createPool(params);
    }

    async insertSomeRowsIntoSomeTable()
    {
        const connection = await this.db.getConnection();
        await connection.execute('START TRANSACTION;');

        try {
            for (let i = 0; i < 100; i++) {
                //get the values to insert from an external service, for example.
                const values = await service.getValues();
                await conn.execute('INSERT SomeTable .... <the values>...');
            }
            await connection.execute('COMMIT');
            await connection.release();
        } 
        catch (e) {
            await connection.execute('ROLLBACK');
            await connection.release();
        }
    }
}

Here you borrow connection from the pool in your insertSomeRowsIntoSomeTable() and if other async functions follow the same pattern this connection will be exclusively used by one transaction code path only until released

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.