node.js + pule połączeń mysql

85

Próbuję dowiedzieć się, jak zorganizować strukturę mojej aplikacji, aby korzystać z MySQL w najbardziej efektywny sposób. Używam modułu node-mysql. Inne wątki sugerowały użycie buforowania połączeń, więc skonfigurowałem mały moduł mysql.js

var mysql = require('mysql');

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

exports.pool = pool;

Teraz za każdym razem, gdy chcę odpytać mysql, potrzebuję tego modułu, a następnie odpytuję bazę danych

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

Czy to dobre podejście? Nie mogłem znaleźć zbyt wielu przykładów użycia połączeń mysql poza bardzo prostym, w którym wszystko odbywa się w głównym skrypcie app.js, więc tak naprawdę nie wiem, jaka jest konwencja / najlepsze praktyki.

Czy po każdym zapytaniu powinienem zawsze używać connection.end ()? A jeśli gdzieś o tym zapomnę?

Jak przepisać część eksportu mojego modułu mysql, aby zwracała tylko połączenie, aby nie musieć za każdym razem pisać getConnection ()?

kasztelan
źródło
2
Dla tych, którzy to znajdą i myślą: „Mam connection.queryw kodzie całe miejsce” - prawdopodobnie czas na refaktoryzację. Budowanie bazy danych klasy abstrakcji, że oferty select, insert, updateitp - i tylko do użytku connection(lub pool) w ramach tej jednej klasy db ...
random_user_name
@random_user_name Czy masz jakieś linki lub kod, który realizuje Twoją sugestię?
KingAndrew
@random_user_name Jak zarządzałbyś transakcjami w tym przypadku? Jeśli zwolnisz połączenie po każdym zapytaniu?
Jeff Ryan
@JeffRyan możesz mieć inne klasy rozszerzające tę klasę db, w których zarządzasz określonymi przypadkami, które wymagają nadzwyczajnych transakcji. Ale myślę, że sugestia random_user_name niekoniecznie dotyczy transakcji ... Generalnie używam podobnego wzorca, w którym tworzę klasę modelu bazowego, która zapewnia podstawowe metody, a metoda wstawiania na przykład wymaga transakcji, ponieważ najpierw wstawia rekord a następnie wybiera według ostatnio wstawionego identyfikatora, aby pobrać wynik.
lucasreta

Odpowiedzi:

68

To dobre podejście.

Jeśli chcesz tylko uzyskać połączenie, dodaj następujący kod do modułu, w którym znajduje się pula:

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

module.exports = getConnection;

Nadal musisz za każdym razem napisać getConnection. Ale możesz zapisać połączenie w module za pierwszym razem.

Nie zapomnij zakończyć połączenia, gdy skończysz z niego korzystać:

connection.release();
Klaasvaak
źródło
18
Tylko jedno ostrzeżenie. To connection.release();teraz, na basenach.
sdanzig
To prawda. Zmieniłem to.
Klaasvaak,
Poza tym, jeśli mogę, sugerowałbym użycie obietnicy zamiast oddzwonienia, ale to tylko preferencja ... mimo to świetne rozwiązanie
Spock
@Spock, czy możesz podać link do przykładu? Wyraźne obietnice są trochę irytujące w dotychczasowej pracy, myślę, że czegoś mi brakuje. Jak dotąd mogę używać tylko var deferred = q.defer (), a następnie rozwiązać lub odrzucić, ale wydaje się, że to dużo narzutu dla czegoś tak prostego. Jeśli tak, dzięki :)
PixMach
1
Możesz również użyć pool.query()bezpośrednio. To jest skrót do przepływu kodu pool.getConnection()-> connection.query()-> connection.release().
Gal Shaboodi
28

Powinieneś unikać używania, pool.getConnection()jeśli możesz. Jeśli dzwonisz pool.getConnection(), musisz zadzwonić, connection.release()gdy skończysz korzystać z połączenia. W przeciwnym razie aplikacja utknie w oczekiwaniu na zawsze, aż połączenia zostaną zwrócone do puli po osiągnięciu limitu połączeń.

W przypadku prostych zapytań możesz użyć pool.query(). Ten skrót automatycznie zadzwoni connection.release()za Ciebie - nawet w przypadku wystąpienia błędów.

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

Jednak w niektórych przypadkach musisz użyć pool.getConnection(). Te przypadki obejmują:

  • Wykonywanie wielu zapytań w ramach transakcji.
  • Udostępnianie obiektów danych, takich jak tymczasowe tabele, między kolejnymi zapytaniami.

Jeśli musisz użyć pool.getConnection(), upewnij się, że dzwonisz, connection.release()używając wzoru podobnego do poniższego:

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

Osobiście wolę używać Promises i useAsync()wzoru. Ten wzorzec w połączeniu z async/ awaitsprawia, że ​​o wiele trudniej jest przypadkowo zapomnieć o release()połączeniu, ponieważ zamienia zakres leksykalny w automatyczne wywołanie .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
źródło
+1 - tylko uwaga - oczekiwanie na każde zapytanie może nie mieć sensu w przypadkach, gdy uruchamiasz wiele zapytań, które w praktyce mogłyby być uruchamiane jednocześnie zamiast sekwencyjnie.
random_user_name
@cale_b Jeśli nie robisz czegoś dziwnie magicznego, równoległe uruchamianie tych zapytań jest niemożliwe. Jeśli uruchamiasz wiele zapytań w transakcji z zależnościami danych, nie możesz uruchomić drugiego zapytania, dopóki nie masz pewności, że pierwsze zostało zakończone. Jeśli Twoje zapytania współużytkują transakcję, jak pokazano, współużytkują również połączenie. Każde połączenie obsługuje tylko jedno zapytanie na raz (nie ma czegoś takiego jak MARS w MySQL).
binki
1
Jeśli faktycznie wykonujesz wiele niezależnych operacji w bazie danych, nic nie stoi na przeszkodzie, aby wywołać usePooledConnectionAsync()wiele razy przed zakończeniem pierwszej. Zauważ, że w przypadku buforowania będziesz chciał się upewnić, że unikasz awaitzdarzeń innych niż wypełnianie zapytania w funkcji, którą przekazujesz jako - w actionAsyncprzeciwnym razie możesz skończyć z zakleszczeniem (np. Uzyskaj ostatnie połączenie z puli, a następnie wywołaj inna funkcja, która próbuje załadować dane przy użyciu puli, która będzie czekać wiecznie, aby spróbować uzyskać własne połączenie z puli, gdy jest pusta).
binki
1
Dzięki za zaangażowanie. Może to być obszar, który nie rozumiem, ale poprzednio (przed przełączeniem się na pule, używając przede wszystkim odpowiedzi, BTW) miałem wiele selekcji działających równolegle (a potem łączę wyniki w mojej logice js po ich powrocie ). Nie sądzę, żeby to było magiczne, ale wydawało się, że dobrą strategią jest NIE awaitjedna, zanim poprosisz o następną. Nie przeprowadziłem teraz żadnej analizy, ale sposób, w jaki stworzyłem rzeczy (zwracanie nowych obietnic), wydaje mi się, że nadal działa równolegle ...
random_user_name
@cale_b Racja, nie mówię, że wzór jest zły. Jeśli potrzebujesz załadować wiele fragmentów danych i można założyć, że są one albo niezależne, albo wystarczająco niezmienne, uruchamianie kilku niezależnych ładunków, a następnie tylko awaitwtedy, gdy faktycznie potrzebujesz ich do wspólnego komponowania wyników, jest sposobem na zrobienie tego (choć obawiam się, że spowoduje to fałszywe pozytywne nieobsłużone zdarzenia odrzucenia obietnicy, które mogą spowodować awarię node.js w przyszłości --unhandled-rejections=strict).
binki
14

Owijka okaże się przydatna :)

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

Wymagaj, używaj tego w ten sposób:

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
źródło
10

Używam tego połączenia klasy bazowej z 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;

Po prostu użyj tego w ten sposób:

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

DB.query("select * from tasks", null, function (data, error) {
   callback(data, error);
});
Sagi Tsofan
źródło
1
Co jeśli zapytanie errjest prawdziwe? czy nie powinien nadal wywoływać callbackz nullparametrem, aby wskazać, że w zapytaniu jest jakiś błąd?
Joe Huang
Tak, piszesz, musisz oddzwonić z błędem zapytania
Sagi Tsofan
Niezłe. Ale powinieneś dodać taki elsewarunek: w przeciwnym razie if (!err) { callback(rows, err); } else { callback(null, err); }aplikacja może się zawiesić. Ponieważ connection.on('error', callback2)nie zajmie się wszystkimi „błędami”. Dzięki!
Tadej
dokładnie, dodałem tę poprawkę
Sagi Tsofan
nodejs newbe tutaj: Dlaczego masz funkcję (dane, błąd) i wywołanie zwrotne (dane, błąd); kiedy większość kodu nodejs, który widziałem, to błąd jako pierwszy parametr, a data / callback jako drugi parametr? np. oddzwonienie (błąd, wyniki)
KingAndrew
2

Gdy skończysz z połączeniem, po prostu zadzwoń, connection.release()a połączenie wróci do puli, gotowe do ponownego użycia przez kogoś innego.

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

Jeśli chcesz zamknąć połączenie i usunąć je z puli, użyj connection.destroy()zamiast tego. Pula utworzy nowe połączenie, gdy będzie potrzebne następnym razem.

Źródło : https://github.com/mysqljs/mysql

Mukesh Chapagain
źródło
0

Korzystając ze standardowej mysql.createPool (), połączenia są leniwie tworzone przez pulę. Jeśli skonfigurujesz pulę tak, aby zezwalała na maksymalnie 100 połączeń, ale zawsze będziesz używać tylko 5 jednocześnie, zostanie nawiązanych tylko 5 połączeń. Jeśli jednak skonfigurujesz go dla 500 połączeń i wykorzystasz wszystkie 500, pozostaną one otwarte przez cały czas trwania procesu, nawet jeśli są bezczynne!

Oznacza to, że jeśli Twój serwer MySQL Server max_connections wynosi 510, twój system będzie miał tylko 10 dostępnych połączeń mySQL, dopóki serwer MySQL ich nie zamknie (w zależności od tego, na co ustawiłeś limit czasu oczekiwania) lub aplikacja zostanie zamknięta! Jedynym sposobem ich zwolnienia jest ręczne zamknięcie połączeń za pośrednictwem instancji puli lub zamknięcie puli.

Moduł mysql-connection-pool-manager został stworzony, aby naprawić ten problem i automatycznie skalować liczbę połączeń w zależności od obciążenia. Nieaktywne połączenia są zamykane, a bezczynne pule połączeń są ostatecznie zamykane, jeśli nie było żadnej aktywności.

    // 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
źródło
-5

zawsze używam connection.relase (); po pool.getconnetion jak

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
źródło
7
Nie myśl, że powinieneś
zwalniać
1
Tak, to zła wiadomość ... jest to efekt uboczny asynchronicznej natury rzeczy, której unikniesz w tej wersji. Jeśli wprowadzisz jakieś opóźnienie, nie zobaczysz tego zapytania. Wzorzec to ... pool.getConnection (function (err, connection) {// Użyj połączenia connection.query ('WYBIERZ coś Z czegoś', function (błąd, wyniki, pola) {// I zakończono połączenie. connection.release (); // Obsługa błędu po wydaniu. if (błąd) zgłoszenie
hpavc