agrupación de conexiones node.js + mysql

85

Estoy tratando de averiguar cómo estructurar mi aplicación para usar MySQL de la manera más eficiente. Estoy usando el módulo node-mysql. Otros hilos aquí sugirieron usar la agrupación de conexiones, así que configuré un pequeño módulo mysql.js

var mysql = require('mysql');

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

exports.pool = pool;

Ahora, cada vez que quiero consultar mysql, necesito este módulo y luego consultar la base de datos

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

¿Es este un buen enfoque? Realmente no pude encontrar demasiados ejemplos de uso de conexiones mysql además de uno muy simple donde todo se hace en el script principal de app.js, así que realmente no sé cuáles son las convenciones / mejores prácticas.

¿Debo usar siempre connection.end () después de cada consulta? ¿Qué pasa si lo olvido en alguna parte?

¿Cómo reescribir la parte de exportaciones de mi módulo mysql para devolver solo una conexión para no tener que escribir getConnection () cada vez?

Kasztelan
fuente
2
Para aquellos que encuentran esto y piensan "tengo connection.querytodo el lugar en mi código", probablemente sea el momento de refactorizar. Construir una clase de abstracción de base de datos que ofrece select, insert, update, etc - y sólo utilizan connection(o pool) dentro de esa clase db sola ...
random_user_name
@random_user_name ¿tiene algún enlace o código que implemente su sugerencia?
KingAndrew
@random_user_name ¿Cómo administraría las transacciones en este caso? ¿Si libera la conexión después de cada consulta?
Jeff Ryan
@JeffRyan puedes tener otras clases que amplíen esta clase db en la que gestionas casos particulares que requieren transacciones extraordinarias. Pero creo que la sugerencia de random_user_name no es necesariamente contra las transacciones ... Generalmente uso un patrón similar, en el que creo una clase de modelo base que proporciona los métodos básicos, y el método de inserción, por ejemplo, requiere transacciones, ya que primero inserta un registro y luego selecciona por último ID insertado para recuperar el resultado.
lucasreta

Respuestas:

68

Es un buen enfoque.

Si solo desea obtener una conexión, agregue el siguiente código a su módulo donde se encuentra el grupo:

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

module.exports = getConnection;

Todavía tienes que escribir getConnection cada vez. Pero puede guardar la conexión en el módulo la primera vez que la obtenga.

No olvide finalizar la conexión cuando haya terminado de usarla:

connection.release();
Klaasvaak
fuente
18
Solo un aviso. Es connection.release();ahora, para piscinas.
sdanzig
Es verdad. Lo cambie.
Klaasvaak
También, si es posible, se recomienda usar una promesa en lugar de devolución de llamada, pero eso es sólo una preferencia ... gran solución, sin embargo
Spock
@Spock, ¿puedes vincular a un ejemplo de esto? Es un poco molesto trabajar con las promesas expresas hasta ahora, creo que me estoy perdiendo algo. Hasta ahora solo puedo usar var deferred = q.defer () y luego resolver o rechazar, pero eso parece una gran sobrecarga para algo tan simple. Si es así, gracias :)
PixMach
1
También puede utilizar pool.query()directamente. Este es un atajo para el pool.getConnection()-> connection.query()-> connection.release()flujo de código.
Gal Shaboodi
27

Debe evitar usarlo pool.getConnection()si puede. Si llama pool.getConnection(), debe llamar connection.release()cuando haya terminado de usar la conexión. De lo contrario, su aplicación se quedará atascada esperando para siempre que las conexiones regresen al grupo una vez que alcance el límite de conexiones.

Para consultas sencillas, puede utilizar pool.query(). Esta abreviatura lo llamará automáticamente connection.release(), incluso en condiciones de error.

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

Sin embargo, en algunos casos debe utilizar pool.getConnection(). Estos casos incluyen:

  • Realización de múltiples consultas dentro de una transacción.
  • Compartir objetos de datos, como tablas temporales, entre consultas posteriores.

Si debe usar pool.getConnection(), asegúrese de llamar connection.release()usando un patrón similar al siguiente:

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

Personalmente prefiero usar Promisesy el useAsync()patrón. Este patrón combinado con async/ awaithace que sea mucho más difícil olvidar accidentalmente release()la conexión porque convierte su alcance léxico en una llamada automática a .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
fuente
+1 (solo una nota) esperar cada consulta puede no tener sentido en los casos en los que está ejecutando varias consultas que en la práctica podrían ejecutarse simultáneamente en lugar de secuencialmente.
random_user_name
@cale_b A menos que esté haciendo algo extrañamente mágico, ejecutar estas consultas en paralelo es imposible. Si está ejecutando varias consultas en una transacción con dependencias de datos, no puede ejecutar la segunda consulta hasta que esté seguro de que se completó la primera. Si sus consultas comparten una transacción, como se demostró, también comparten una conexión. Cada conexión admite solo una consulta a la vez (no existe MARS en MySQL).
binki
1
Si de hecho está realizando varias operaciones independientes en la base de datos, nada le impide llamar usePooledConnectionAsync()varias veces antes de completar la primera. Tenga en cuenta que, con la agrupación, querrá asegurarse de evitar awaiteventos distintos a la finalización de la consulta dentro de la función que pasa; de lo actionAsynccontrario, podría terminar creando un interbloqueo (por ejemplo, obtener la última conexión de una agrupación y luego llamar otra función que intenta cargar datos usando el grupo que esperará una eternidad para intentar obtener su propia conexión del grupo cuando esté vacío).
binki
1
Gracias por participar. Esta puede ser un área que mi comprensión es débil, pero, anteriormente (antes de cambiar a grupos, usando su respuesta principalmente, por cierto) tenía múltiples selecciones ejecutándose en "paralelo" (y luego fusiono los resultados en mi lógica js después de que regresan ). No creo que eso sea mágico, pero me pareció una buena estrategia NO awaituna antes de pedir la siguiente. No he hecho ningún análisis ahora, pero por la forma en que he escrito las cosas (devolviendo nuevas promesas), creo que todavía se está ejecutando en paralelo ...
random_user_name
@cale_b Correcto, no estoy diciendo que ese patrón sea malo. Si necesita cargar varios datos y se puede suponer que son independientes o lo suficientemente invariables, awaituna forma de hacerlo es lanzar un montón de cargas independientes y luego solo usarlas cuando realmente las necesite para componer los resultados juntos. (aunque tengo miedo de que eso resulte en eventos de rechazo de promesas no controladas de falso positivo que podrían bloquear node.js en el futuro --unhandled-rejections=strict).
binki
14

Encontrará esta envoltura ú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;
            }
        };
    }
};

Requerirlo, úselo así:

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 Jiménez
fuente
10

Estoy usando esta conexión de clase base con 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;

Úselo así:

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

DB.query("select * from tasks", null, function (data, error) {
   callback(data, error);
});
Sagi Tsofan
fuente
1
¿Qué pasa si la consulta erres verdadera? ¿No debería seguir llamando callbackcon el nullparámetro para indicar que hay algún error en la consulta?
Joe Huang
Sí, escribe, necesita hacer una devolución de llamada con el error de consulta
Sagi Tsofan
Buena esa. Pero debe agregar una elsecondición como esta: de lo if (!err) { callback(rows, err); } else { callback(null, err); }contrario, su aplicación podría bloquearse. Porque connection.on('error', callback2)no se ocupará de todos los "errores". ¡Gracias!
Tadej
exactamente, agregué esta solución
Sagi Tsofan
nodejs newbe here: ¿Por qué tiene función (datos, error) y devolución de llamada (datos, error); cuando la mayor parte de todo el código de nodejs que he visto es un error como primer parámetro y el data / callback como segundo parámetro? ex: devolución de llamada (error, resultados)
KingAndrew
2

Cuando haya terminado con una conexión, simplemente llame connection.release()y la conexión volverá a la piscina, lista para ser utilizada nuevamente por otra persona.

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

Si desea cerrar la conexión y eliminarla del grupo, utilice connection.destroy()en su lugar. El grupo creará una nueva conexión la próxima vez que se necesite una.

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

Mukesh Chapagain
fuente
0

Usando el estándar mysql.createPool (), el grupo crea conexiones de manera perezosa. Si configura el grupo para permitir hasta 100 conexiones, pero solo usa 5 simultáneamente, solo se realizarán 5 conexiones. Sin embargo, si lo configura para 500 conexiones y usa las 500, permanecerán abiertas durante la duración del proceso, ¡incluso si están inactivas!

Esto significa que si su servidor MySQL max_connections es 510, su sistema solo tendrá 10 conexiones mySQL disponibles hasta que su servidor MySQL las cierre (depende de lo que haya configurado su wait_timeout) o su aplicación se cierre. La única forma de liberarlos es cerrar manualmente las conexiones a través de la instancia del grupo o cerrar el grupo.

El módulo mysql-connection-pool-manager se creó para solucionar este problema y escalar automáticamente el número de conexiones dependiendo de la carga. Las conexiones inactivas se cierran y los grupos de conexiones inactivos se cierran eventualmente si no ha habido ninguna actividad.

    // 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
fuente
-5

yo siempre uso connection.relase (); después de 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
fuente
7
No crea que debería liberar la conexión antes de usarla para realizar consultas
kwhitley
1
Sí, esta es una mala noticia ... es un efecto secundario de la naturaleza asincrónica de las cosas que se está saliendo con la suya con esta versión. Si introduce alguna latencia, no verá esa consulta. El patrón es ... pool.getConnection (function (err, connection) {// Usa la conexión connection.query ('SELECT something FROM sometable', function (error, results, fields) {// Y listo con la conexión. connection.release (); // Manejar el error después del lanzamiento. if (error) throw error; npmjs.com/package/mysql#pooling-connections
hpavc