pool de conexão node.js + mysql

85

Estou tentando descobrir como estruturar meu aplicativo para usar o MySQL da maneira mais eficiente. Estou usando o módulo node-mysql. Outros tópicos aqui sugeriram o uso de pool de conexão, então eu configurei um pequeno módulo mysql.js

var mysql = require('mysql');

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

exports.pool = pool;

Agora, sempre que eu quiser consultar o mysql, eu exijo este módulo e, em seguida, consulte o banco de dados

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

Essa é uma boa abordagem? Eu realmente não consegui encontrar muitos exemplos de uso de conexões mysql além de um muito simples onde tudo é feito no script app.js principal, então eu realmente não sei quais são as convenções / melhores práticas.

Devo sempre usar connection.end () após cada consulta? E se eu esquecer em algum lugar?

Como reescrever a parte de exportações do meu módulo mysql para retornar apenas uma conexão, então eu não tenho que escrever getConnection () todas as vezes?

Kasztelan
fonte
2
Para aqueles que encontram isso e pensam "eu tenho connection.querytudo em meu código" - provavelmente é hora de refatorar. Criar uma classe de abstração de banco de dados que oferece select, insert, update, etc - e só usar connection(ou pool) dentro dessa única classe db ...
random_user_name
@random_user_name, você tem algum link ou código que implemente sua sugestão?
KingAndrew
@random_user_name Como você gerenciaria as transações neste caso? Se você liberar a conexão após cada consulta?
Jeff Ryan
@JeffRyan você pode ter outras classes que estendem esta classe de banco de dados nas quais você gerencia casos particulares que requerem transações extraordinárias. Mas acho que a sugestão de random_user_name não é necessariamente contra transações ... Eu geralmente uso um padrão semelhante, no qual crio uma classe de modelo base que fornece os métodos básicos, e o método de inserção, por exemplo, requer transações, pois primeiro insere um registro e então seleciona pelo último ID inserido para recuperar o resultado.
lucasreta

Respostas:

68

É uma boa abordagem.

Se você deseja apenas obter uma conexão, adicione o seguinte código ao seu módulo onde o pool está:

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

module.exports = getConnection;

Você ainda precisa escrever getConnection todas as vezes. Mas você pode salvar a conexão no módulo na primeira vez que a obtiver.

Não se esqueça de encerrar a conexão quando terminar de usá-la:

connection.release();
Klaasvaak
fonte
18
Apenas um aviso. É connection.release();agora, para piscinas.
sdanzig
Isso é verdade. Eu mudei isso.
Klaasvaak
Além disso, se eu puder, sugiro usar uma promessa em vez de retorno de chamada, mas isso é apenas uma preferência ... ótima solução, no entanto
Spock
@Spock você pode criar um link para um exemplo disso? As promessas expressas são meio chatas de trabalhar até agora, acho que estou perdendo alguma coisa. Até agora, só posso usar var deferred = q.defer () e então resolver ou rejeitar, mas isso parece muito overhead para algo tão simples. Se sim, obrigado :)
PixMach
1
Você também pode usar pool.query()diretamente. Este é um atalho para o fluxo de código pool.getConnection()-> connection.query()-> connection.release().
Gal Shaboodi
30

Você deve evitar o uso, pool.getConnection()se puder. Se você ligar pool.getConnection(), deverá ligar connection.release()quando terminar de usar a conexão. Caso contrário, seu aplicativo ficará paralisado esperando para sempre que as conexões sejam retornadas ao pool assim que você atingir o limite de conexão.

Para consultas simples, você pode usar pool.query(). Esta abreviação irá chamá connection.release()-lo automaticamente - mesmo em condições de erro.

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

No entanto, em alguns casos, você deve usar pool.getConnection(). Esses casos incluem:

  • Fazer várias consultas em uma transação.
  • Compartilhamento de objetos de dados, como tabelas temporárias, entre consultas subsequentes.

Se for necessário usar pool.getConnection(), certifique-se de chamar connection.release()usando um padrão semelhante ao abaixo:

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

Eu pessoalmente prefiro usar se Promiseo useAsync()padrão. Esse padrão combinado com async/ awaittorna muito mais difícil esquecer acidentalmente release()a conexão porque transforma seu escopo léxico em uma chamada automática para .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}`);
}
binki
fonte
1
+1 - apenas uma nota - aguardar cada consulta pode não fazer sentido em instâncias onde você está executando várias consultas que poderiam na prática ser executadas simultaneamente em vez de sequencialmente.
random_user_name de
1
@cale_b A menos que você esteja fazendo algo estranhamente mágico, executar essas consultas em paralelo é impossível. Se você estiver executando várias consultas em uma transação com dependências de dados, não poderá executar a segunda consulta até ter certeza de que a primeira foi concluída. Se suas consultas estão compartilhando uma transação, como demonstrado, elas também estão compartilhando uma conexão. Cada conexão suporta apenas uma consulta por vez (não existe MARS no MySQL).
binki de
1
Se você de fato estiver executando várias operações independentes no banco de dados, nada o impedirá de fazer usePooledConnectionAsync()várias chamadas antes de concluir a primeira. Observe que, com o pooling, você vai querer ter certeza de evitar awaiteventos além da conclusão da consulta dentro da função que você passou - caso actionAsynccontrário, você pode acabar criando um deadlock (por exemplo, obter a última conexão de um pool e chamar outra função que tenta carregar dados usando o pool que vai esperar uma eternidade para tentar obter sua própria conexão do pool quando vazio).
binki de
1
Obrigado pelo envolvimento. Esta pode ser uma área que meu entendimento é fraco - mas, anteriormente (antes de mudar para pools, usando sua resposta principalmente, BTW) eu tinha vários selects rodando em "paralelo" (e então mesclo os resultados em minha lógica js depois que eles voltam ) Não acho que seja mágico, mas parecia uma boa estratégia NÃO fazer awaituma antes de pedir a próxima. Eu não fiz nenhuma análise agora, mas do jeito que eu criei coisas (retornando novas promessas), acho que ainda está funcionando em paralelo ...
random_user_name
@cale_b Certo, não estou dizendo que o padrão seja ruim. Se você precisa carregar vários dados e pode-se presumir que eles são independentes ou suficientemente imutáveis, lançar um monte de carregamentos independentes e, em seguida, apenas awaitcarregá-los quando você realmente precisar deles para compor os resultados juntos é uma maneira de fazer isso (embora eu esteja com medo de que isso resulte em eventos de rejeição de promessa falsos positivos não tratados, que podem travar o node.js no futuro --unhandled-rejections=strict).
binki de
14

Você achará este wrapper útil :)

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

Exigir, use assim:

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});
          });
Felipe Jimenez
fonte
10

Estou usando esta conexão de classe base com 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;

Basta usá-lo assim:

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

DB.query("select * from tasks", null, function (data, error) {
   callback(data, error);
});
Sagi Tsofan
fonte
1
E se a consulta errfor verdadeira? ainda não deveria chamar callbackcom nullparâmetro para indicar que há algum erro na consulta?
Joe Huang
Sim, você escreve, é necessário enviar um retorno de chamada com o erro de consulta
Sagi Tsofan
Agradável. Mas você deve adicionar uma elsecondição como esta: caso if (!err) { callback(rows, err); } else { callback(null, err); }contrário, seu aplicativo pode travar. Porque connection.on('error', callback2)não vai cuidar de todos os "erros". Obrigado!
Tadej
exatamente, adicionei esta correção
Sagi Tsofan,
nodejs newbe here: Por que você tem função (dados, erro) e retorno de chamada (dados, erro); quando a maior parte de todo o código nodejs que vi é erro como o primeiro parâmetro e data / callback como o segundo parâmetro? ex: retorno de chamada (erro, resultados)
KingAndrew
2

Ao terminar a conexão, basta ligar connection.release()e a conexão retornará ao pool, pronta para ser utilizada novamente por outra pessoa.

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

Se quiser fechar a conexão e removê-la do pool, use connection.destroy(). O pool criará uma nova conexão na próxima vez que uma for necessária.

Fonte : https://github.com/mysqljs/mysql

Mukesh Chapagain
fonte
0

Usando o mysql.createPool () padrão, as conexões são criadas lentamente pelo pool. Se você configurar o pool para permitir até 100 conexões, mas sempre usar 5 simultaneamente, apenas 5 conexões serão feitas. No entanto, se você configurá-lo para 500 conexões e usar todas as 500, elas permanecerão abertas durante a duração do processo, mesmo se estiverem ociosas!

Isso significa que se seu servidor MySQL max_connections for 510, seu sistema terá apenas 10 conexões mySQL disponíveis até que seu MySQL Server as feche (depende de como você configurou o wait_timeout) ou seu aplicativo feche! A única maneira de liberá-los é fechar manualmente as conexões por meio da instância do pool ou fechar o pool.

O módulo mysql-connection-pool-manager foi criado para corrigir esse problema e escalar automaticamente o número de conexões dependentes da carga. As conexões inativas são fechadas e os pools de conexão inativos são eventualmente fechados se não houver nenhuma atividade.

    // 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();

Ref: https://www.npmjs.com/package/mysql-connection-pool-manager

Yordan
fonte
-6

eu sempre uso connection.relase (); após pool.getconnetion como

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);
        }
    });
Alex
fonte
8
Não pense que você deve liberar a conexão antes de usá-la para consultar
kwhitley
2
Sim, esta é uma má notícia ... é um efeito colateral da natureza assíncrona das coisas que você está conseguindo com este lançamento. Se você introduzir alguma latência, não verá essa consulta. O padrão é ... pool.getConnection (function (err, connection) {// Use a conexão connection.query ('SELECIONE algo DE algures', função (erro, resultados, campos) {// E pronto com a conexão. connection.release (); // Manipular o erro após o lançamento. if (error) lançar o erro; npmjs.com/package/mysql#pooling-connections
hpavc