node.js + MySQL-Verbindungspooling

85

Ich versuche herauszufinden, wie ich meine Anwendung so strukturieren kann, dass MySQL auf effizienteste Weise verwendet wird. Ich verwende das Node-MySQL-Modul. Andere Threads hier schlugen vor, Verbindungspooling zu verwenden, also richtete ich ein kleines Modul mysql.js ein

var mysql = require('mysql');

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

exports.pool = pool;

Wann immer ich MySQL abfragen möchte, benötige ich dieses Modul und frage dann die Datenbank ab

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

Ist das ein guter Ansatz? Ich konnte nicht wirklich viele Beispiele für die Verwendung von MySQL-Verbindungen finden, außer einer sehr einfachen, bei der alles im Skript main app.js ausgeführt wird, sodass ich die Konventionen / Best Practices nicht wirklich kenne.

Sollte ich nach jeder Abfrage immer connection.end () verwenden? Was ist, wenn ich es irgendwo vergesse?

Wie schreibe ich den Exportteil meines MySQL-Moduls neu, um nur eine Verbindung zurückzugeben, damit ich nicht jedes Mal getConnection () schreiben muss?

Kasztelan
quelle
2
Für diejenigen, die dies finden und denken "Ich habe connection.queryüberall in meinem Code" - es ist wahrscheinlich Zeit für eine Umgestaltung. Erstellen Sie eine Datenbank - Abstraktions Klasse, die Angebote select, insert, update, usw. - und nur verwenden connection(oder poolinnerhalb dieser einzigen db - Klasse) ...
random_user_name
@random_user_name Haben Sie Links oder Code, der Ihren Vorschlag umsetzt?
KingAndrew
@random_user_name Wie würden Sie in diesem Fall Transaktionen verwalten? Wenn Sie die Verbindung nach jeder Abfrage freigeben?
Jeff Ryan
@ JeffRyan Sie können andere Klassen haben, die diese Datenbankklasse erweitern, in denen Sie bestimmte Fälle verwalten, die außergewöhnliche Transaktionen erfordern. Aber ich denke, der Vorschlag von random_user_name ist nicht unbedingt gegen Transaktionen ... Ich verwende im Allgemeinen ein ähnliches Muster, in dem ich eine Basismodellklasse erstelle, die die grundlegenden Methoden bereitstellt, und die Einfügemethode erfordert beispielsweise Transaktionen, da sie zuerst einen Datensatz einfügt und wählt dann nach der zuletzt eingefügten ID aus, um das Ergebnis abzurufen.
Lucasreta

Antworten:

68

Es ist ein guter Ansatz.

Wenn Sie nur eine Verbindung herstellen möchten, fügen Sie Ihrem Modul, in dem sich der Pool befindet, den folgenden Code hinzu:

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

module.exports = getConnection;

Sie müssen immer noch jedes Mal getConnection schreiben. Sie können die Verbindung jedoch beim ersten Erhalt im Modul speichern.

Vergessen Sie nicht, die Verbindung zu beenden, wenn Sie fertig sind:

connection.release();
Klaasvaak
quelle
18
Nur ein Kopf hoch. Es ist connection.release();jetzt für Pools.
sdanzig
Das ist richtig. Ich habe es geändert.
Klaasvaak
Wenn ich darf, würde ich auch vorschlagen, ein Versprechen anstelle eines Rückrufs zu verwenden, aber das ist nur eine Präferenz ... trotzdem eine großartige Lösung
Spock
@Spock kannst du auf ein Beispiel dafür verlinken? Express-Versprechen sind bisher etwas nervig, ich glaube, ich vermisse etwas. Bisher kann ich nur var deferred = q.defer () verwenden und dann auflösen oder ablehnen, aber das scheint viel Aufwand für etwas so Einfaches zu sein. Wenn ja, danke :)
PixMach
1
Sie können auch pool.query()direkt verwenden. Dies ist eine Verknüpfung für den Codefluss pool.getConnection()-> connection.query()-> connection.release().
Gal Shaboodi
28

Sie sollten die Verwendung vermeiden, pool.getConnection()wenn Sie können. Wenn Sie anrufen pool.getConnection(), Sie müssen rufen Sie, connection.release()wenn Sie die Verbindung nicht mehr verwenden. Andernfalls bleibt Ihre Anwendung hängen und wartet ewig darauf, dass Verbindungen zum Pool zurückgegeben werden, sobald Sie das Verbindungslimit erreicht haben.

Für einfache Abfragen können Sie verwenden pool.query(). Diese Abkürzung ruft automatisch connection.release()nach Ihnen - auch unter Fehlerbedingungen.

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

In einigen Fällen müssen Sie jedoch verwenden pool.getConnection(). Diese Fälle umfassen:

  • Mehrere Abfragen innerhalb einer Transaktion durchführen.
  • Freigeben von Datenobjekten wie temporären Tabellen zwischen nachfolgenden Abfragen.

Wenn Sie verwenden müssen pool.getConnection(), stellen Sie sicher, dass Sie connection.release()mit einem ähnlichen Muster wie unten anrufen :

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

Ich persönlich bevorzuge Promises und das useAsync()Muster. Dieses Muster in Kombination mit async/ awaitmacht es viel schwieriger, release()die Verbindung versehentlich zu vergessen, da es Ihr lexikalisches Scoping in einen automatischen Aufruf verwandelt an .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
quelle
+1 - nur eine Anmerkung - das Warten auf jede Abfrage ist möglicherweise nicht sinnvoll, wenn Sie mehrere Abfragen ausführen, die in der Praxis gleichzeitig statt nacheinander ausgeführt werden könnten.
random_user_name
@cale_b Wenn Sie nicht etwas seltsam Magisches tun, ist es unmöglich, diese Abfragen parallel auszuführen. Wenn Sie mehrere Abfragen in einer Transaktion mit Datenabhängigkeiten ausführen, können Sie die zweite Abfrage erst ausführen, wenn Sie sicher sind, dass die erste abgeschlossen ist. Wenn Ihre Abfragen, wie gezeigt, eine Transaktion gemeinsam nutzen, teilen sie auch eine Verbindung. Jede Verbindung unterstützt jeweils nur eine Abfrage ( in MySQL gibt es kein MARS ).
Binki
1
Wenn Sie tatsächlich mehrere unabhängige Vorgänge in der Datenbank ausführen, hindert Sie nichts daran, usePooledConnectionAsync()vor Abschluss des ersten Vorgangs mehrmals anzurufen. Beachten Sie, dass Sie beim Pooling sicherstellen möchten, dass Sie awaitandere Ereignisse als den Abschluss der Abfrage innerhalb der Funktion actionAsyncvermeiden, die Sie als übergeben. Andernfalls kann es zu einem Deadlock kommen (z. B. die letzte Verbindung aus einem Pool abrufen und dann aufrufen) eine andere Funktion, die versucht, Daten über den Pool zu laden, die ewig warten wird, um zu versuchen, eine eigene Verbindung aus dem Pool zu erhalten, wenn sie leer ist).
Binki
1
Vielen Dank für Ihr Engagement. Dies mag ein Bereich sein, den ich nur schwach verstehe - aber zuvor (vor dem Wechsel zu Pools, hauptsächlich mit Ihrer Antwort BTW) wurden mehrere Auswahlen "parallel" ausgeführt (und dann füge ich die Ergebnisse in meiner js-Logik zusammen, nachdem sie zurückgekehrt sind ). Ich denke nicht, dass das magisch ist, aber es schien eine gute Strategie zu sein, NICHT awaiteine zu sein, bevor man nach der nächsten fragt. Ich habe noch keine Analyse durchgeführt, aber so wie ich Dinge verfasst habe (neue Versprechen zurückgeben), denke ich, dass es immer noch parallel läuft ...
random_user_name
@cale_b Richtig, ich sage nicht, dass das Muster schlecht ist. Wenn Sie mehrere Daten laden müssen und davon ausgegangen werden kann, dass sie entweder unabhängig oder ausreichend unverändert sind, können Sie eine Reihe unabhängiger Ladevorgänge starten und sie dann nur dann awaitausführen, wenn Sie sie tatsächlich benötigen, um die Ergebnisse zusammenzustellen (obwohl ich Angst habe, dass dies zu falsch positiven, nicht behandelten Versprechungsverweigerungsereignissen führen würde, mit denen node.js in Zukunft abstürzen könnte --unhandled-rejections=strict).
Binki
14

Sie finden diesen Wrapper nützlich :)

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

Benötigen Sie es, verwenden Sie es wie folgt:

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
quelle
10

Ich verwende diese Basisklassenverbindung mit 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;

Verwenden Sie es einfach so:

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

DB.query("select * from tasks", null, function (data, error) {
   callback(data, error);
});
Sagi Tsofan
quelle
1
Was ist, wenn die Abfrage errwahr ist? Sollte es nicht immer noch callbackmit dem nullParameter aufgerufen werden, um anzuzeigen, dass die Abfrage einen Fehler enthält?
Joe Huang
Ja, Sie schreiben, müssen Rückruf mit dem Abfragefehler
sprudeln
Schön. Sie sollten jedoch eine elseBedingung wie die folgende hinzufügen : if (!err) { callback(rows, err); } else { callback(null, err); }Andernfalls kann Ihre Anwendung hängen bleiben. Weil connection.on('error', callback2)sich nicht um alle "Fehler" kümmern. Vielen Dank!
Tadej
genau, ich habe dieses
Update
nodejs newbe here: Warum haben Sie Funktion (Daten, Fehler) und Rückruf (Daten, Fehler); Wann ist vor allem der Code von nodejs, den ich gesehen habe, ein Fehler als erster Parameter und der Daten- / Rückruf als zweiter Parameter? Beispiel: Rückruf (Fehler, Ergebnisse)
KingAndrew
2

Wenn Sie mit einer Verbindung fertig sind, rufen Sie einfach an connection.release()und die Verbindung kehrt zum Pool zurück und kann von einer anderen Person erneut verwendet werden.

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

Wenn Sie die Verbindung schließen und aus dem Pool entfernen möchten, verwenden Sie connection.destroy()stattdessen. Der Pool erstellt eine neue Verbindung, wenn das nächste Mal eine benötigt wird.

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

Mukesh Chapagain
quelle
0

Mit dem Standard mysql.createPool () werden Verbindungen vom Pool träge erstellt. Wenn Sie den Pool so konfigurieren, dass bis zu 100 Verbindungen zulässig sind, aber immer nur 5 gleichzeitig verwendet werden, werden nur 5 Verbindungen hergestellt. Wenn Sie es jedoch für 500 Verbindungen konfigurieren und alle 500 verwenden, bleiben diese für die Dauer des Prozesses geöffnet, auch wenn sie inaktiv sind!

Das heißt, wenn Ihr MySQL Server max_connections 510 ist, stehen Ihrem System nur 10 mySQL-Verbindungen zur Verfügung, bis Ihr MySQL Server sie schließt (abhängig davon, auf was Sie Ihr wait_timeout eingestellt haben) oder Ihre Anwendung schließt! Die einzige Möglichkeit, sie freizugeben, besteht darin, die Verbindungen manuell über die Poolinstanz zu schließen oder den Pool zu schließen.

Das Modul mysql-connection-pool-manager wurde erstellt, um dieses Problem zu beheben und die Anzahl der Verbindungen abhängig von der Last automatisch zu skalieren. Inaktive Verbindungen werden geschlossen und inaktive Verbindungspools werden schließlich geschlossen, wenn keine Aktivität stattgefunden hat.

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

Ich benutze immer connection.relase (); nach pool.getconnetion wie

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
quelle
7
Denken Sie nicht, dass Sie die Verbindung freigeben sollten, bevor Sie sie zum Abfragen verwenden
kwhitley
1
Ja, das sind schlechte Nachrichten ... es ist ein Nebeneffekt der asynchronen Natur von Dingen, die Sie mit dieser Version durchkommen. Wenn Sie eine Latenz einführen, wird diese Abfrage nicht angezeigt. Das Muster lautet ... pool.getConnection (Funktion (Fehler, Verbindung) {// Verwenden Sie die Verbindung connection.query ('SELECT some FROM sometable', Funktion (Fehler, Ergebnisse, Felder) {// Und fertig mit der Verbindung. connection.release (); // Fehler nach der Veröffentlichung behandeln. if (Fehler) throw error; npmjs.com/package/mysql#pooling-connections
hpavc