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, мені потрібен цей модуль, а потім запитую databse

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

Це правда. Я його змінив.
Klaasvaak

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

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

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

27

Вам слід уникати використання, 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 - лише примітка - очікування кожного запиту може не мати сенсу в тих випадках, коли ви запускаєте кілька запитів, які на практиці можуть виконуватися одночасно, а не послідовно.
random_user_name

@cale_b Якщо ви не робите чогось дивно магічного, паралельно виконувати ці запити неможливо. Якщо ви виконуєте кілька запитів у транзакції із залежностями даних, ви не можете запустити другий запит, поки не переконаєтесь, що перший виконано. Якщо ваші запити мають спільну транзакцію, як було продемонстровано, вони також мають спільний зв’язок. Кожне з'єднання одночасно підтримує лише один запит ( у MySQL такого поняття, як MARS ) немає.
binki

1
Якщо ви насправді виконуєте кілька незалежних операцій у базі даних, ніщо не заважає вам телефонувати usePooledConnectionAsync()кілька разів до завершення першої. Зауважте, що при об’єднанні в пул ви хочете уникати awaitінших подій, крім завершення запиту, у межах функції, яку ви actionAsyncпередаєте як - інакше ви можете створити глухий кут (наприклад, отримати останнє з’єднання з пулу, а потім зателефонувати інша функція, яка намагається завантажити дані за допомогою пулу, який буде чекати вічно, щоб спробувати отримати власне з'єднання з пулу, коли він порожній).
binki

1
Дякуємо за участь. Це може бути область, яку я розумію слабко, - але раніше (до переходу на пули, переважно використовуючи вашу відповідь, до речі), у мене було кілька виділень, які працювали паралельно (а потім я зливаю результати в моїй 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параметром , щоб вказати , є якась - то помилка в запиті?
Joe Huang

Так, ви пишете, вам потрібно
здути

Хороший. Але слід додати таку elseумову: if (!err) { callback(rows, err); } else { callback(null, err); }інакше ваша програма може зависнути. Тому що connection.on('error', callback2)не подбає про всі "помилки". Дякую!
Тадей

точно, я додав це виправлення
Сагі Цофан,

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

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, вони залишаться відкритими протягом усього процесу, навіть якщо вони не працюють!

Це означає, що якщо ваш max_connections сервера MySQL дорівнює 510, у вашій системі буде доступно лише 10 з'єднань mySQL, поки ваш сервер MySQL їх не закриє (залежить від того, на що ви встановили час очікування) або не закриє вашу програму! Єдиний спосіб їх звільнити - це вручну закрити з'єднання через екземпляр пулу або закрити пул.

Модуль 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


-5

я завжди використовую connection.relase (); після pool.getconnection типу

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

7
Не думайте, що ви повинні звільнити з’єднання, перш ніж використовувати його для запиту
kwhitley

1
Так, це погана новина .... це побічний ефект асинхронного характеру речей, від яких ви виходите цим релізом. Якщо ви введете деяку затримку, ви не побачите цього запиту. Шаблон: ... pool.getConnection (функція (помилка, підключення) {// Використовуйте підключення connection.query ('ВИБЕРІТЬ щось із деякого змінного', функція (помилка, результати, поля) {// І зроблено з підключенням. connection.release (); // Обробляти помилку після випуску. if (error) error error; npmjs.com/package/mysql#pooling-connections
hpavc
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.