Node.js mssql несколько одновременных подключений к серверам SQL, мешающих друг другу

#sql #node.js #sql-server #express #concurrency

#sql #node.js #sql-сервер #экспресс #параллелизм

Вопрос:

Я использую mssql в своем Node.js экспресс-приложение для установления соединений со многими различными базами данных на многих разных серверах SQL.

Я создал следующий пример, чтобы продемонстрировать общую структуру моего кода:

 app.get('/api/example'), async (request, response) => {

    // FYI I may be using await incorrect here since I'm new to it, just using it here for code simplicity
    let results1 = await GetDataFromSqlServerA()
    let results2 = await GetDataFromSqlServerB()

    response.status(200).send([results1, results2])
});

function GetDataFromSqlServerA() {
    return new Promise(function(resolve, reject)    {

        let sql = require("mssql")

        let sqlConnectionDetails = {
            user: 'test',
            password: 'foobar',
            server: 'SQLServerA', 
            database: 'DatabaseA'
        }

        sql.connect(sqlConnectionDetails, function (error)    {

            let sqlRequest = new sql.Request()
            let queryText = 'SELECT * FROM TableA'

            sqlRequest.query(queryText, function (error, results) {
                sql.close()
                resolve(results)
            })
        })
    })
}

function GetDataFromSqlServerB() {
        return new Promise(function(resolve, reject)    {

        let sql = require("mssql")

        let sqlConnectionDetails = {
            user: 'test',
            password: 'foobar',
            server: 'SQLServerB', 
            database: 'DatabaseB'
        }

        sql.connect(sqlConnectionDetails, function (error)    {

            let sqlRequest = new sql.Request()
            let queryText = 'SELECT * FROM TableB'

            sqlRequest.query(queryText, function (error, results) {
                sql.close()
                resolve(results)
            })
        })
    })
}
  

У меня есть запрос, который выполняет асинхронный поиск данных из двух отдельных местоположений SQL server. Первый выполняемый вызов SQL выполняется нормально, но второй завершается Invalid object 'Table2' ошибкой. Он не может найти таблицу, потому что второй вызов по какой-то причине получает сведения о соединении для первого вызова. Это указывает на неправильный SQL server и базу данных!

Я бы подумал, что это невозможно, потому что функции находятся в их собственных областях. Первый вызов SQL ничего не должен знать о втором и наоборот — или, по крайней мере, я бы подумал.

Я также попытался определить один экземпляр sql глобально, но возникают те же проблемы.

У меня может быть одна функция, которая устанавливает SQL-соединение с сервером A, отправляет запрос на сервер A, отключается от сервера A, устанавливает SQL-соединение с сервером B и, наконец, отправляет запрос на сервер B. Однако, когда вступает в игру асинхронность и SQL запускается параллельными запросами, у меня естьпроблемы.

Надеюсь, я просто тупой, так как я новичок в асинхронном коде, пожалуйста, просветите меня! Спасибо!

Комментарии:

1. Даже если вы предоставляете отдельные строки подключения к sql.connect() Я подозреваю, что вы можете столкнуться с глобальным пулом подключений. Вы уже ознакомились с Advanced Pool Management ?

2. Спасибо @AlwaysLearning, я как-то не натыкался на это раньше. Я вижу, где у меня проблемы. См. Следующий параграф «Для облегчения управления пулом в вашем приложении доступна функция global connect (), которая доступна для использования. Начиная с версии 6 этой библиотеки, разработчик может выполнять повторные вызовы этой функции для получения глобального пула подключений. Это означает, что вам не нужно отслеживать пул в вашем приложении (как это было раньше). Если глобальный пул уже подключен, он будет разрешен в подключенном пуле.». Мне нужно кое-что сделать!

Ответ №1:

Подумал, что я бы обновил это на случай, если кто-нибудь еще столкнется с подобной проблемой. @AlwaysLearning предоставил мне ссылку на раздел документации mssql npm, в котором объясняется объединение пулов соединений и выполнение этого, когда необходим доступ к нескольким базам данных. Я бы рекомендовал прочитать его.

Оказывается, что функция .connect() mssql является глобальной. Если глобальный пул уже подключен при вызове функции .connect(), он будет разрешен для уже подключенного пула. Это вызывало у меня проблемы, потому что в моем приложении при быстрой отправке двух запросов возникало следующее:

  1. Запрос 1 будет подключаться к базе данных A
  2. Запрос 2 попытается подключиться к базе данных B
  3. Поскольку база данных A уже подключена к глобальному пулу, запрос 2 выбирает соединение с базой данных A
  4. SQL-запрос запроса 2 завершается ошибкой, поскольку он недействителен для базы данных A

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

В документации npm приведена пара примеров вспомогательных файлов, но я обнаружил, что они не идеальны для меня, и я не смог скомпилировать весь код в своей среде. Я настроил код следующим образом:

mssql-connection-pooling.js:

 const { ConnectionPool } = require('mssql')
const pools = {}
 
// create a new connection pool
function CreatePool(config) {
    let key = JSON.stringify(config)

    if (GetPool(key))
        throw new Error('Pool already exists')

    pools[key] = (new ConnectionPool(config)).connect()
    return pools[key]
}

// get a connection pool from all pools
function GetPool(name) {
  if (pools[name])
    return pools[name]
  else
    return null
}

// if pool already exists, return it, otherwise create it
function GetCreateIfNotExistPool(config)  {
    let key = JSON.stringify(config)

    let pool = GetPool(key)
    if(pool)
        return pool
    else
        return CreatePool(config)
}

// close a single pool
function ClosePool(config) {
    let key = JSON.stringify(config)

    if (pools[key]) {
        const pool = pools[key];
        delete pools[key];
        pool.close()
        return true
    }
    return false
}

// close all the pools
function CloseAllPools() {
    pools.forEach((pool) => {
        pool.close()
    })
    pools = {}
    return true
}
 
module.exports = {
  ClosePool,
  CloseAllPools,
  CreatePool,
  GetPool,
  GetCreateIfNotExistPool
}
  

Я создал функцию GetCreateIfNotExistPool(), которую вы предоставляете объекту конфигурации подключения к базе данных. Функция проверяет, есть ли открытое соединение, сохраненное в пулах для данной конфигурации соединения. Если есть, он просто возвращает пул подключений. Если нет, он создает его, а затем возвращает.

Пример использования:

 const sql = require("mssql");
let mssql = require('./mssql-pool-management.js')

let exampleDBConfigA = {
    user: 'test',
    password: 'password,
    server: 'SqlServerA', 
    database: 'DatabaseA'
};
let exampleDBConfigB = {
    user: 'test',
    password: 'password,
    server: 'SqlServerB', 
    database: 'DatabaseB'
};

...

// Request 1
try {
    let sqlPool = await mssql.GetCreateIfNotExistPool(exampleDBConfigA)
    let request = new sql.Request(sqlPool)

    // query code
}
catch(error) {
    //error handling
}

...

// Request 2
try {
    let sqlPool = await mssql.GetCreateIfNotExistPool(exampleDBConfigB)
    let request = new sql.Request(sqlPool)

    // query code
}
catch(error) {
    //error handling
}

  

В этом примере запросы 1 и 2 могут вызываться одновременно просто отлично, без вмешательства их соединений!

Комментарии:

1. Сработало как шарм, спасибо! В моем коде есть некоторое асинхронное ожидание, поэтому пул сохранялся как обещание, поэтому метод pool.close() в ClosePool() терпел неудачу, сообщая, что «pool.close не является функцией». Я просто добавил async и await в функцию createPool(), и затем она смогла закрыть пул без сбоев. « асинхронная функция createPool(config) { let key = JSON.stringify(config) if (GetPool(key)) выдает новую ошибку (‘Пул уже существует’) пулы [ключ] = await (новый пул соединений (config)).connect() возвращает пулы [ключ] }«

2. @Ampig Я также должен поблагодарить вас! Это было именно то, что я искал. Спасибо за реализацию и обработку пула таким образом в отдельном модуле.

3. @Ampig спасибо вам за все ваши усилия здесь. Я использовал stack overflow в течение многих лет, и это, безусловно, один из самых полезных сообщений и ответов.