Почему происходит сбой асинхронной / ожидающей версии моего транзакционного приложения mssql, но работает версия обратных вызовов?

Чтобы осветить проблему, у меня работает приложение nodejs / mssql, я попытался написать две функционально эквивалентные версии простого (подготовленного) оператора INSERT, заключенного в транзакцию.

Версия callbacks работает - вставляет строку в мою базу данных Sql Server.

Асинхронная / ожидающая версия выдает ошибку -

TransactionError: Can't commit transaction. There is a request in progress.

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

Спасибо!

var sql = require('mssql');  // mssql: 4.1.0; tedious: 2.2.4; node: v8.4.0

var cfg = {
    "db": "sqlserver",
    "domain": "XXXXXX",
    "user": "cseelig",
    "password": "xxxxxx",
    "server": "xxxxxx.xxxxxx.xxxxxx.xxxxxx",
    "port": 1433,
    "stream": false,
    "options": { 
        "trustedConnection": true
    },
    "requestTimeout": 900000,
    "connectionTimeout": 30000,
    "pool": {
        "max": 3,
        "min": 0,
        "idleTimeoutMillis": 30000
    }
};

var statement = "insert into wng_dw.dbo.D_LIB_Google_Search_Query (query, LastUpdateDate) values (@query, GetDate())";

// I only run one or the other -

main1("12347");   // fails
main2("98765:);   // works

async function main1(val) {

    try {
        const conn = await new sql.connect(cfg);
        const transaction = new sql.Transaction();
        await transaction.begin();
        const ps = new sql.PreparedStatement(transaction);
        ps.input('query', sql.VarChar(200));
        await ps.prepare(statement);
        await ps.execute( {"query": val} );
        await ps.unprepare();
        await transaction.commit();
        sql.close;
    } catch(err){
        console.log("Error: " + err);
    };

    process.exit(0);

}


async function main2(val) {

    sql.connect(cfg, err => {
        const transaction = new sql.Transaction();
        transaction.begin(err => {
            const ps = new sql.PreparedStatement(transaction);
            ps.input('query', sql.VarChar(200));
            ps.prepare(statement, err => {
                ps.execute( {"query": val}, (err, result) => {
                    ps.unprepare(err => { 
                        transaction.commit(err => {
                            sql.close();
                        });
                    });
                });
            });
        });
    });

}

2 ответа

Transaction.begin не возвращает обещание. Вы могли бы просто обещать это. Что-то вроде следующего:

await new Promise(resolve => transaction.begin(resolve));
const request = new sql.Request(transaction);
//...
await transaction.commit();

После фиксации и отката объект "запрос" больше не может быть использован. В противном случае он покажет ошибку относительно транзакции не начался....

Надеюсь, это поможет.

Прежде чем вы сможете зафиксировать или откатить транзакцию, все операторы должны быть подготовлены.

Вы также должны дождаться оператора unprepare , иначе запрос все еще выполняется, а обещание выполнить еще не разрешено.

Используйте небольшую обертку, чтобы упростить задачу:

      import * as dotenv from 'dotenv'
import mssql from 'mssql'

dotenv.config()

const sqlServerConfig = {
  server: process.env.SQL_SERVER,
  user: process.env.QS_USER,
  password: process.env.QS_PASS,
  options: { enableArithAbort: false },
}

let pool: mssql.ConnectionPool
let transaction: mssql.Transaction
const statements: mssql.PreparedStatement[] = []

export const connect = async (): Promise<void> => {
  pool = new mssql.ConnectionPool({ ...sqlServerConfig, database: process.env.DATABASE })
  await pool.connect()
}

export const disconnect = async (): Promise<void> => {
  if (typeof pool == 'undefined') return
  if (pool.connected) await pool.close()
}

export const begin = async (): Promise<void> => {
  transaction = new mssql.Transaction(pool)
  await transaction.begin()
}

export const unprepare = async (statement: mssql.PreparedStatement): Promise<void> => {
  if (typeof statement == 'undefined') return
  if (statement.prepared) await statement.unprepare()
}

export const commit = async (): Promise<void> => {
  await transaction.commit()
}

export const rollback = async (): Promise<void> => {
  for (const statement of statements) {
    await unprepare(statement)
  }
  if (typeof transaction == 'undefined') return
  await transaction.rollback()
}

export const createStatement = (): mssql.PreparedStatement => {
  const statement = new mssql.PreparedStatement(transaction)
  statements.push(statement)
  return statement
}

Применение:

      try {
  await connect()
  await begin()

  const myStatement = createStatement()

  ..... bind parameters
  ..... prepare statement

  for ( ..... ) {
    await myStatement.execute( ..... )
  }

  await unprepare(myStatement)

  await commit()
  await disconnect()
  exit(0)
}
catch(e) {
  log.error(e)
  await rollback()
  await disconnect()
  exit(1)
}

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

Другие вопросы по тегам