nodo.js + pool di connessioni mysql


85

Sto cercando di capire come strutturare la mia applicazione per utilizzare MySQL in modo più efficiente. Sto usando il modulo node-mysql. Altri thread qui hanno suggerito di utilizzare il pool di connessioni, quindi ho impostato un piccolo modulo mysql.js

var mysql = require('mysql');

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

exports.pool = pool;

Ora ogni volta che voglio interrogare mysql ho bisogno di questo modulo e quindi interrogare il database

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

È un buon approccio? Non sono riuscito a trovare troppi esempi di utilizzo di connessioni mysql oltre a quello molto semplice in cui tutto è fatto nello script principale di app.js, quindi non so davvero quali siano le convenzioni / migliori pratiche.

Devo sempre usare connection.end () dopo ogni query? E se me ne dimentico da qualche parte?

Come riscrivere la parte delle esportazioni del mio modulo mysql per restituire solo una connessione in modo da non dover scrivere getConnection () ogni volta?


2
A coloro che lo trovano e pensano "Ho connection.querytutto il posto nel mio codice" - è probabilmente il momento di eseguire il refactoring. Costruire una classe di astrazione del database che offre select, insert, update, ecc - e solo utilizzano connection(o pool) all'interno di tale singola classe db ...
random_user_name

@random_user_name hai link o codice che implementa il tuo suggerimento?
KingAndrew

@random_user_name Come gestireste le transazioni in questo caso? Se rilasci la connessione dopo ogni query?
Jeff Ryan,

@ JeffRyan puoi avere altre classi che estendono questa classe db in cui gestisci casi particolari che richiedono transazioni straordinarie. Ma penso che il suggerimento di random_user_name non sia necessariamente contro le transazioni ... In genere utilizzo un modello simile, in cui creo una classe del modello di base che fornisce i metodi di base e il metodo di inserimento, ad esempio, richiede transazioni, poiché prima inserisce un record e quindi seleziona in base all'ultimo ID inserito per recuperare il risultato.
lucasreta

Risposte:


68

È un buon approccio.

Se vuoi solo ottenere una connessione, aggiungi il seguente codice al tuo modulo in cui si trova il pool:

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

module.exports = getConnection;

Devi ancora scrivere getConnection ogni volta. Ma potresti salvare la connessione nel modulo la prima volta che lo ottieni.

Non dimenticare di terminare la connessione quando hai finito di usarla:

connection.release();

18
Solo un avviso. E ' connection.release();ora, per le piscine.
sdanzig

È vero. L'ho cambiato.
Klaasvaak

Inoltre, se posso permettermi, suggerirei di usare una promessa invece della richiamata, ma questa è solo una preferenza ... comunque un'ottima soluzione
Spock

@Spock puoi collegarmi a un esempio di questo? Le promesse espresse sono un po 'fastidiose con cui lavorare finora, penso che mi manchi qualcosa. Finora posso solo usare var deferred = q.defer () e poi risolvere o rifiutare, ma sembra un sacco di overhead per qualcosa di così semplice. Se è così, grazie :)
PixMach

1
Puoi anche usare pool.query()direttamente. Questa è una scorciatoia per il flusso di codice pool.getConnection()-> connection.query()-> connection.release().
Gal Shaboodi

30

Dovresti evitare di usare pool.getConnection()se puoi. Se chiami pool.getConnection(), devi chiamare connection.release()quando hai finito di usare la connessione. In caso contrario, l'applicazione si bloccherà in attesa per sempre che le connessioni vengano restituite al pool una volta raggiunto il limite di connessione.

Per domande semplici, puoi usare pool.query(). Questa scorciatoia ti chiamerà automaticamente connection.release(), anche in condizioni di errore.

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

Tuttavia, in alcuni casi è necessario utilizzare pool.getConnection(). Questi casi includono:

  • Esecuzione di più query all'interno di una transazione.
  • Condivisione di oggetti dati come tabelle temporanee tra query successive.

Se è necessario utilizzare pool.getConnection(), assicurarsi di chiamare connection.release()utilizzando un modello simile a quello riportato di seguito:

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 preferisco usare se Promiseil useAsync()pattern. Questo modello combinato con async/ awaitrende molto più difficile dimenticare accidentalmente release()la connessione perché trasforma il tuo ambito lessicale in una chiamata automatica 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}`);
}

1
+1 - solo una nota - l'attesa di ogni query potrebbe non avere senso nei casi in cui si eseguono più query che potrebbero in pratica essere eseguite contemporaneamente invece che in sequenza.
random_user_name

1
@cale_b A meno che tu non stia facendo qualcosa di stranamente magico, eseguire queste query in parallelo è impossibile. Se si eseguono più query in una transazione con dipendenze dai dati, non è possibile eseguire la seconda query finché non si è certi che la prima è stata completata. Se le tue query condividono una transazione, come dimostrato, condividono anche una connessione. Ogni connessione supporta solo una query alla volta (non esiste MARS in MySQL).
binki

1
Se infatti stai eseguendo più operazioni indipendenti nel database, nulla ti impedisce di chiamare usePooledConnectionAsync()più volte prima del completamento della prima. Nota che, con il pooling, vorrai assicurarti di evitare awaiteventi diversi dal completamento della query all'interno della funzione che passi come actionAsync, altrimenti potresti finire per creare un deadlock (ad esempio, ottenere l'ultima connessione da un pool, quindi chiamare un'altra funzione che prova a caricare i dati utilizzando il pool che aspetterà per sempre per provare a ottenere la propria connessione dal pool quando è vuoto).
binki

1
Grazie per il coinvolgimento. Questa potrebbe essere un'area di mia comprensione debole - ma, in precedenza (prima di passare ai pool, usando principalmente la tua risposta, BTW) avevo più selezioni in esecuzione in "parallelo" (e poi unisco i risultati nella mia logica js dopo che sono tornati ). Non penso che sia magico, ma sembrava una buona strategia NON awaitprima di chiedere il prossimo. Non ho fatto alcuna analisi ora, ma il modo in cui ho creato le cose (restituendo nuove promesse), penso che sia ancora in esecuzione in parallelo ...
random_user_name

@cale_b Giusto, non sto dicendo che lo schema è cattivo. Se è necessario caricare più dati e si può presumere che siano indipendenti o sufficientemente immutabili, avviare un gruppo di carichi indipendenti e poi awaitinserirli solo quando ne hai effettivamente bisogno per comporre i risultati insieme è un modo per farlo (anche se temo che ciò comporterebbe eventi di rifiuto della promessa non gestiti falsi positivi che potrebbero arrestare in futuro node.js con --unhandled-rejections=strict).
binki

14

Troverai utile questo wrapper :)

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

Richiedilo, usalo in questo modo:

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

Sto usando questa connessione di classe 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;

Usalo in questo modo:

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

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

1
E se la query errfosse vera? non dovrebbe ancora chiamare callbackcon nullparametro per indicare che c'è qualche errore nella query?
Joe Huang

Sì, scrivi, devi richiamare la bolla con l'errore di query
Sagi Tsofan

Ben fatto. Ma dovresti aggiungere una elsecondizione come questa: if (!err) { callback(rows, err); } else { callback(null, err); }altrimenti la tua applicazione potrebbe bloccarsi. Perché connection.on('error', callback2)non si prenderà cura di tutti gli "errori". Grazie!
Tadej

esattamente, ho aggiunto questa correzione
Sagi Tsofan

nodejs newbe qui: Perché hai funzione (dati, errore) e callback (dati, errore); quando la maggior parte di tutto il codice nodejs che ho visto è errore come primo parametro e data / callback come secondo parametro? es: callback (errore, risultati)
KingAndrew

2

Quando hai finito con una connessione, chiama connection.release()e la connessione tornerà al pool, pronta per essere utilizzata di nuovo da qualcun altro.

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 desideri chiudere la connessione e rimuoverla dal pool, usa connection.destroy()invece. Il pool creerà una nuova connessione la prossima volta che sarà necessaria.

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


0

Usando lo standard mysql.createPool (), le connessioni vengono create pigramente dal pool. Se si configura il pool in modo da consentire fino a 100 connessioni, ma se ne utilizzano solo 5 contemporaneamente, verranno effettuate solo 5 connessioni. Tuttavia, se lo configuri per 500 connessioni e utilizzi tutte e 500, rimarranno aperte per la durata del processo, anche se sono inattive!

Ciò significa che se il tuo server MySQL max_connections è 510, il tuo sistema avrà solo 10 connessioni mySQL disponibili fino a quando il tuo server MySQL non le chiude (dipende da cosa hai impostato su wait_timeout) o la tua applicazione si chiude! L'unico modo per liberarli è chiudere manualmente le connessioni tramite l'istanza del pool o chiudere il pool.

Il modulo mysql-connection-pool-manager è stato creato per risolvere questo problema e ridimensionare automaticamente il numero di connessioni in base al carico. Le connessioni inattive vengono chiuse ei pool di connessioni inattivi vengono infine chiusi se non si è verificata alcuna attività.

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

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


-6

uso sempre connection.relase (); dopo pool.getconnetion piace

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
Non pensare che dovresti rilasciare la connessione prima di usarla per interrogare
kwhitley

2
Sì, questa è una cattiva notizia ... è un effetto collaterale della natura asincrona delle cose che stai facendo franca con questa versione. Se introduci una certa latenza, non vedrai quella query. Lo schema è ... pool.getConnection (function (err, connection) {// Use the connection connection.query ('SELECT something FROM Sometable', function (error, results, fields) {// E fatto con la connessione. connection.release (); // Gestire l'errore dopo il rilascio. if (error) genera l'errore; npmjs.com/package/mysql# carpooling
connections
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.