node.js + пул соединений mysql


85

Я пытаюсь понять, как структурировать свое приложение для наиболее эффективного использования MySQL. Я использую модуль node-mysql. Другие потоки здесь предложили использовать пул соединений, поэтому я установил небольшой модуль mysql.js

var mysql = require('mysql');

var pool  = mysql.createPool({
    host     : 'localhost',
    user     : 'root',
    password : 'root',
    database : 'guess'
});

exports.pool = pool;

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

var mysql = require('../db/mysql').pool;

var test = function(req, res) {
     mysql.getConnection(function(err, conn){
         conn.query("select * from users", function(err, rows) {
              res.json(rows);
         })
     })
}

Это хороший подход? Я не смог найти слишком много примеров использования соединений mysql, кроме очень простого, где все делается в основном сценарии app.js, поэтому я действительно не знаю, каковы соглашения / лучшие практики.

Должен ли я всегда использовать connection.end () после каждого запроса? Что, если я где-нибудь об этом забуду?

Как переписать часть экспорта моего модуля mysql, чтобы возвращать только соединение, чтобы мне не приходилось каждый раз писать getConnection ()?


2
Тем, кто находит это и думает: «В connection.queryмоем коде есть все места» - вероятно, пришло время провести рефакторинг. Построить класс абстракции базы данных , которая предлагает select, insert, updateи т.д. - и только использовать connection(или pool) в пределах этого одного класса дб ...
random_user_name

@random_user_name у вас есть ссылки или код, реализующий ваше предложение?
KingAndrew

@random_user_name Как бы вы управляли транзакциями в этом случае? Если после каждого запроса отпускать соединение?
Джефф Райан

@JeffRyan у вас могут быть другие классы, которые расширяют этот класс db, в котором вы управляете конкретными случаями, требующими экстраординарных транзакций. Но я думаю, что предложение random_user_name не обязательно против транзакций ... Я обычно использую аналогичный шаблон, в котором я создаю класс базовой модели, который предоставляет базовые методы, а метод вставки, например, требует транзакций, поскольку он сначала вставляет запись а затем выбирает по последнему вставленному идентификатору для получения результата.
lucasreta

Ответы:


68

Это хороший подход.

Если вы просто хотите установить соединение, добавьте следующий код в свой модуль, в котором находится пул:

var getConnection = function(callback) {
    pool.getConnection(function(err, connection) {
        callback(err, connection);
    });
};

module.exports = getConnection;

Вам все равно придется каждый раз писать getConnection. Но вы можете сохранить соединение в модуле при первом подключении.

Не забудьте разорвать соединение, когда закончите его использовать:

connection.release();

18
Просто предупреждаю. Это connection.release();сейчас, для бассейнов.
sdanzig

Это правда. Я изменил это.
Клаасваак

Кроме того, если можно, я бы предложил использовать обещание вместо обратного вызова, но это всего лишь предпочтение ... тем не менее, отличное решение,
Спок

@Spock можете ли вы дать ссылку на пример этого? Экспресс-обещания пока раздражают работать, думаю, что-то упускаю. Пока я могу использовать только var deferred = q.defer (), а затем разрешить или отклонить, но это похоже на большие накладные расходы для чего-то настолько простого. Если да, то спасибо :)
PixMach

1
Вы также можете использовать pool.query()напрямую. Это ярлык для потока кода pool.getConnection()-> connection.query()-> connection.release().
Гал Шабуди

30

Вам следует избегать использования, pool.getConnection()если можете. Если вы звоните pool.getConnection(), вы должны позвонить, connection.release()когда закончите использовать соединение. В противном случае ваше приложение застрянет в ожидании возврата соединений в пул, как только вы достигнете предела количества подключений.

Для простых запросов вы можете использовать pool.query(). Это сокращение будет автоматически вызывать connection.release()вас - даже в случае ошибки.

function doSomething(cb) {
  pool.query('SELECT 2*2 "value"', (ex, rows) => {
    if (ex) {
      cb(ex);
    } else {
      cb(null, rows[0].value);
    }
  });
}

Однако в некоторых случаях необходимо использовать pool.getConnection(). Эти случаи включают:

  • Выполнение нескольких запросов в рамках транзакции.
  • Совместное использование объектов данных, таких как временные таблицы, между последующими запросами.

Если вы должны использовать pool.getConnection(), убедитесь, что вы вызываете connection.release()с использованием шаблона, подобного приведенному ниже:

function doSomething(cb) {
  pool.getConnection((ex, connection) => {
    if (ex) {
      cb(ex);
    } else {
      // Ensure that any call to cb releases the connection
      // by wrapping it.
      cb = (cb => {
        return function () {
          connection.release();
          cb.apply(this, arguments);
        };
      })(cb);
      connection.beginTransaction(ex => {
        if (ex) {
          cb(ex);
        } else {
          connection.query('INSERT INTO table1 ("value") VALUES (\'my value\');', ex => {
            if (ex) {
              cb(ex);
            } else {
              connection.query('INSERT INTO table2 ("value") VALUES (\'my other value\')', ex => {
                if (ex) {
                  cb(ex);
                } else {
                  connection.commit(ex => {
                    cb(ex);
                  });
                }
              });
            }
          });
        }
      });
    }
  });
}

Лично я предпочитаю использовать Promises и useAsync()узор. Этот шаблон в сочетании с async/ awaitделает намного сложнее случайно забыть release()о соединении, потому что он превращает вашу лексическую область видимости в автоматический вызов .release():

async function usePooledConnectionAsync(actionAsync) {
  const connection = await new Promise((resolve, reject) => {
    pool.getConnection((ex, connection) => {
      if (ex) {
        reject(ex);
      } else {
        resolve(connection);
      }
    });
  });
  try {
    return await actionAsync(connection);
  } finally {
    connection.release();
  }
}

async function doSomethingElse() {
  // Usage example:
  const result = await usePooledConnectionAsync(async connection => {
    const rows = await new Promise((resolve, reject) => {
      connection.query('SELECT 2*4 "value"', (ex, rows) => {
        if (ex) {
          reject(ex);
        } else {
          resolve(rows);
        }
      });
    });
    return rows[0].value;
  });
  console.log(`result=${result}`);
}

1
+1 - просто примечание - ожидание каждого запроса может не иметь смысла в случаях, когда вы выполняете несколько запросов, которые на практике могут выполняться одновременно, а не последовательно.
random_user_name

1
@cale_b Если вы не делаете что-то сверхъестественное, параллельное выполнение этих запросов невозможно. Если вы выполняете несколько запросов в транзакции с зависимостями данных, вы не можете выполнить второй запрос, пока не будете уверены, что первый завершился. Если ваши запросы совместно используют транзакцию, как показано, они также используют соединение. Каждое соединение поддерживает только один запрос за раз ( в MySQL нет такой вещи, как MARS ).
binki

1
Если вы фактически выполняете несколько независимых операций в базе данных, ничто не мешает вам выполнить несколько вызовов usePooledConnectionAsync()до завершения первой. Обратите внимание, что при использовании пула вы должны быть уверены, что избегаете awaitсобытий, отличных от завершения запроса в функции, которую вы передаете как - в actionAsyncпротивном случае вы можете создать тупик (например, получить последнее соединение из пула, а затем вызвать другая функция, которая пытается загрузить данные, используя пул, который будет вечно ждать, чтобы попытаться получить собственное соединение из пустого пула).
binki

1
Спасибо за участие. Это может быть область, в которой я слабо разбираюсь, но раньше (перед переключением на пулы, используя ваш ответ в первую очередь, BTW) у меня было несколько выборок, работающих «параллельно» (а затем я объединяю результаты в моей логике js после того, как они возвращаются ). Я не думаю, что это волшебно, но это казалось хорошей стратегией НЕ делать awaitодно, прежде чем просить о следующем. Сейчас я не проводил никакого анализа, но,
random_user_name

@cale_b Верно, я не говорю, что шаблон плохой. Если вам нужно загрузить несколько фрагментов данных и можно предположить, что они либо независимы, либо в достаточной степени неизменны, запуск нескольких независимых загрузок и последующая загрузка их только тогда, awaitкогда они действительно нужны для объединения результатов, - способ сделать это. (хотя я боюсь, что это приведет к ложным срабатываниям необработанных событий отклонения обещаний, которые могут привести к сбою node.js в будущем --unhandled-rejections=strict).
binki

14

Вы найдете эту обертку полезной :)

var pool = mysql.createPool(config.db);

exports.connection = {
    query: function () {
        var queryArgs = Array.prototype.slice.call(arguments),
            events = [],
            eventNameIndex = {};

        pool.getConnection(function (err, conn) {
            if (err) {
                if (eventNameIndex.error) {
                    eventNameIndex.error();
                }
            }
            if (conn) { 
                var q = conn.query.apply(conn, queryArgs);
                q.on('end', function () {
                    conn.release();
                });

                events.forEach(function (args) {
                    q.on.apply(q, args);
                });
            }
        });

        return {
            on: function (eventName, callback) {
                events.push(Array.prototype.slice.call(arguments));
                eventNameIndex[eventName] = callback;
                return this;
            }
        };
    }
};

Требуйте это, используйте это так:

db.connection.query("SELECT * FROM `table` WHERE `id` = ? ", row_id)
          .on('result', function (row) {
            setData(row);
          })
          .on('error', function (err) {
            callback({error: true, err: err});
          });

10

Я использую это соединение базового класса с mysql:

"base.js"

var mysql   = require("mysql");

var pool = mysql.createPool({
    connectionLimit : 10,
    host: Config.appSettings().database.host,
    user: Config.appSettings().database.username,
    password: Config.appSettings().database.password,
    database: Config.appSettings().database.database
});


var DB = (function () {

    function _query(query, params, callback) {
        pool.getConnection(function (err, connection) {
            if (err) {
                connection.release();
                callback(null, err);
                throw err;
            }

            connection.query(query, params, function (err, rows) {
                connection.release();
                if (!err) {
                    callback(rows);
                }
                else {
                    callback(null, err);
                }

            });

            connection.on('error', function (err) {
                connection.release();
                callback(null, err);
                throw err;
            });
        });
    };

    return {
        query: _query
    };
})();

module.exports = DB;

Просто используйте это так:

var DB = require('../dal/base.js');

DB.query("select * from tasks", null, function (data, error) {
   callback(data, error);
});

1
Что, если запрос errверен? не должен он по- прежнему называть callbackс nullпараметром , чтобы указать , есть какая - то ошибка в запросе?
Джо Хуанг

Да, вы пишете, нужно
вывести

Хороший. Но вы должны добавить такое elseусловие: if (!err) { callback(rows, err); } else { callback(null, err); }иначе ваше приложение может зависнуть. Потому connection.on('error', callback2)что не позаботится обо всех «ошибках». Благодаря!
Тадей

точно, я добавил это исправление
Саги Цофан

nodejs newbe здесь: Почему у вас есть функция (данные, ошибка) и обратный вызов (данные, ошибка); когда больше всего кода nodejs, который я видел, является ошибкой в ​​качестве первого параметра и данных / обратного вызова в качестве второго параметра?
пример

2

Когда вы закончите с подключением, просто позвоните, connection.release()и соединение вернется в пул, готовый к повторному использованию кем-то другим.

var mysql = require('mysql');
var pool  = mysql.createPool(...);

pool.getConnection(function(err, connection) {
  // Use the connection
  connection.query('SELECT something FROM sometable', function (error, results, fields) {
    // And done with the connection.
    connection.release();

    // Handle error after the release.
    if (error) throw error;

    // Don't use the connection here, it has been returned to the pool.
  });
});

Если вы хотите закрыть соединение и удалить его из пула, используйте connection.destroy() вместо этого. Пул создаст новое соединение, когда оно понадобится в следующий раз.

Источник : https://github.com/mysqljs/mysql


0

Используя стандартный mysql.createPool (), соединения лениво создаются пулом. Если вы сконфигурируете пул так, чтобы разрешить до 100 подключений, но использовать только 5 одновременно, будет выполнено только 5 подключений. Однако, если вы настроите его на 500 подключений и используете все 500, они останутся открытыми в течение всего процесса, даже если они простаивают!

Это означает, что если ваш MySQL Server max_connections равен 510, ваша система будет иметь только 10 подключений mySQL, пока ваш MySQL Server не закроет их (в зависимости от того, что вы установили для вашего wait_timeout) или ваше приложение не закроется! Единственный способ освободить их - вручную закрыть соединения через экземпляр пула или закрыть пул.

Модуль mysql-connection-pool-manager был создан для решения этой проблемы и автоматического масштабирования количества подключений в зависимости от нагрузки. Неактивные соединения закрываются, а пулы незанятых соединений в конечном итоге закрываются, если не было никакой активности.

    // Load modules
const PoolManager = require('mysql-connection-pool-manager');

// Options
const options = {
  ...example settings
}

// Initialising the instance
const mySQL = PoolManager(options);

// Accessing mySQL directly
var connection = mySQL.raw.createConnection({
  host     : 'localhost',
  user     : 'me',
  password : 'secret',
  database : 'my_db'
});

// Initialising connection
connection.connect();

// Performing query
connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
  if (error) throw error;
  console.log('The solution is: ', results[0].solution);
});

// Ending connection
connection.end();

Ссылка: https://www.npmjs.com/package/mysql-connection-pool-manager


-6

я всегда использую connection.relase (); после pool.getconnetion вроде

pool.getConnection(function (err, connection) {
      connection.release();
        if (!err)
        {
            console.log('*** Mysql Connection established with ', config.database, ' and connected as id ' + connection.threadId);
            //CHECKING USERNAME EXISTENCE
            email = receivedValues.email
            connection.query('SELECT * FROM users WHERE email = ?', [email],
                function (err, rows) {
                    if (!err)
                    {
                        if (rows.length == 1)
                        {
                            if (bcrypt.compareSync(req.body.password, rows[0].password))
                            {
                                var alldata = rows;
                                var userid = rows[0].id;
                                var tokendata = (receivedValues, userid);
                                var token = jwt.sign(receivedValues, config.secret, {
                                    expiresIn: 1440 * 60 * 30 // expires in 1440 minutes
                                });
                                console.log("*** Authorised User");
                                res.json({
                                    "code": 200,
                                    "status": "Success",
                                    "token": token,
                                    "userData": alldata,
                                    "message": "Authorised User!"
                                });
                                logger.info('url=', URL.url, 'Responce=', 'User Signin, username', req.body.email, 'User Id=', rows[0].id);
                                return;
                            }
                            else
                            {
                                console.log("*** Redirecting: Unauthorised User");
                                res.json({"code": 200, "status": "Fail", "message": "Unauthorised User!"});
                                logger.error('*** Redirecting: Unauthorised User');
                                return;
                            }
                        }
                        else
                        {
                            console.error("*** Redirecting: No User found with provided name");
                            res.json({
                                "code": 200,
                                "status": "Error",
                                "message": "No User found with provided name"
                            });
                            logger.error('url=', URL.url, 'No User found with provided name');
                            return;
                        }
                    }
                    else
                    {
                        console.log("*** Redirecting: Error for selecting user");
                        res.json({"code": 200, "status": "Error", "message": "Error for selecting user"});
                        logger.error('url=', URL.url, 'Error for selecting user', req.body.email);
                        return;
                    }
                });
            connection.on('error', function (err) {
                console.log('*** Redirecting: Error Creating User...');
                res.json({"code": 200, "status": "Error", "message": "Error Checking Username Duplicate"});
                return;
            });
        }
        else
        {
            Errors.Connection_Error(res);
        }
    });

8
Не думайте, что вам следует
разрывать

2
Да, это плохие новости ... это побочный эффект асинхронной природы вещей, которые вам сходит с рук в этом выпуске. Если вы введете некоторую задержку, вы не увидите этот запрос. Шаблон такой ... pool.getConnection (function (err, connection) {// Используйте соединение connection.query ('SELECT something FROM sometable', function (error, results, fields) {// И готово с подключением. connection.release (); // Обработка ошибки после выпуска. if (error) throw error; npmjs.com/package/mysql#pooling-connections
hpavc
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.