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 ()?
connection.query
w kodzie całe miejsce” - prawdopodobnie czas na refaktoryzację. Budowanie bazy danych klasy abstrakcji, że ofertyselect
,insert
,update
itp - i tylko do użytkuconnection
(lubpool
) w ramach tej jednej klasy db ...Odpowiedzi:
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();
źródło
connection.release();
teraz, na basenach.pool.query()
bezpośrednio. To jest skrót do przepływu kodupool.getConnection()
->connection.query()
->connection.release()
.Powinieneś unikać używania,
pool.getConnection()
jeśli możesz. Jeśli dzwoniszpool.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 zadzwoniconnection.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ą: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ć
Promise
s iuseAsync()
wzoru. Ten wzorzec w połączeniu zasync
/await
sprawia, że o wiele trudniej jest przypadkowo zapomnieć orelease()
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}`); }
źródło
usePooledConnectionAsync()
wiele razy przed zakończeniem pierwszej. Zauważ, że w przypadku buforowania będziesz chciał się upewnić, że unikaszawait
zdarzeń innych niż wypełnianie zapytania w funkcji, którą przekazujesz jako - wactionAsync
przeciwnym 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).await
jedna, 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 ...await
wtedy, 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
).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}); });
źródło
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); });
źródło
err
jest prawdziwe? czy nie powinien nadal wywoływaćcallback
znull
parametrem, aby wskazać, że w zapytaniu jest jakiś błąd?else
warunek: w przeciwnym razieif (!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!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
źródło
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
źródło
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); } });
źródło