node.js + mysql bağlantı havuzu oluşturma


85

MySQL'i en verimli şekilde kullanmak için uygulamamı nasıl yapılandıracağımı anlamaya çalışıyorum. Node-mysql modülünü kullanıyorum. Buradaki diğer iş parçacıkları bağlantı havuzunu kullanmayı önerdi, bu yüzden küçük bir modül mysql.js kurdum

var mysql = require('mysql');

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

exports.pool = pool;

Şimdi mysql'i sorgulamak istediğimde bu modüle ihtiyacım var ve ardından veritabanını sorguluyorum

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

Bu iyi bir yaklaşım mı? Her şeyin main app.js betiğinde yapıldığı çok basit olan mysql bağlantılarının yanı sıra gerçekten çok fazla örnek bulamadım, bu yüzden kuralın / en iyi uygulamaların ne olduğunu gerçekten bilmiyorum.

Her sorgudan sonra her zaman connection.end () kullanmalı mıyım? Ya bir yerde unutursam?

Her seferinde getConnection () yazmak zorunda kalmamam için mysql modülümün dışa aktarım bölümünü yalnızca bir bağlantı döndürmek için nasıl yeniden yazabilirim?


2
Bunu bulan ve "Kodumda connection.queryher yerim var" diye düşünenler için - muhtemelen yeniden düzenleme zamanı gelmiştir. Teklifler bir veritabanı soyutlama sınıfı kurmak select, insert, updateve sadece kullanmak - vb connection(ya pool... o tek db sınıfının içinde)
random_user_name

@random_user_name önerinizi uygulayan herhangi bir bağlantı veya kodunuz var mı?
KingAndrew

@random_user_name Bu durumda işlemleri nasıl yönetirsiniz? Her sorgudan sonra bağlantıyı keserseniz?
Jeff Ryan

@JeffRyan, olağanüstü işlemler gerektiren belirli durumları yönettiğiniz bu db sınıfını genişleten başka sınıflara sahip olabilirsiniz. Ancak, random_user_name'nin önerisinin işlemlere karşı olmadığını düşünüyorum ... Genelde benzer bir model kullanıyorum, burada temel yöntemleri sağlayan bir temel model sınıfı oluşturuyorum ve örneğin ekleme yöntemi, ilk önce bir kayıt eklediği için işlemler gerektiriyor ve sonra sonucu almak için son eklenen kimliğe göre seçim yapar.
lucasreta

Yanıtlar:


68

Bu iyi bir yaklaşım.

Sadece bir bağlantı almak istiyorsanız, havuzun bulunduğu modülünüze aşağıdaki kodu ekleyin:

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

module.exports = getConnection;

Yine de her seferinde getConnection yazmanız gerekiyor. Ancak bağlantıyı ilk aldığınızda modülde kaydedebilirsiniz.

Bitirdiğinizde bağlantıyı sonlandırmayı unutmayın:

connection.release();

18
Sadece aklınızda bulunsun. Bu var connection.release();havuzları için, şimdi.
sdanzig

Bu doğru. Ben değiştirdim.
Klaasvaak

Ayrıca, eğer izin verirsem, geri arama yerine söz kullanmayı öneririm, ama bu sadece bir tercih ... yine de harika bir çözüm
Spock

@Spock bunun bir örneğine bağlantı verebilir misiniz? Ekspres vaatler şimdiye kadar çalışmak biraz can sıkıcı, sanırım bir şey kaçırıyorum. Şimdiye kadar sadece var deferred = q.defer () kullanabilir ve sonra çözebilir veya reddedebilirim, ancak bu kadar basit bir şey için çok fazla ek yük gibi görünüyor. Öyleyse, teşekkürler :)
PixMach

1
pool.query()Doğrudan da kullanabilirsiniz . Bu, pool.getConnection()-> connection.query()-> connection.release()kod akışı için bir kısayoldur .
Gal Shaboodi

30

Yapabiliyorsanız kullanmaktan kaçınmalısınız pool.getConnection(). Eğer ararsanız pool.getConnection(), bağlantıyı kullanmayı bitirdiğinizde aramanız gerekirconnection.release() . Aksi takdirde, uygulamanız bağlantı sınırına ulaştığınızda bağlantıların havuza geri dönmesi için sonsuza kadar beklerken takılır.

Basit sorgular için kullanabilirsiniz pool.query(). Bu kısaltma, connection.release()hata durumlarında bile otomatik olarak sizi arayacaktır .

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

Ancak bazı durumlarda kullanmanız gerekir pool.getConnection(). Bu durumlar şunları içerir:

  • Bir işlem içinde birden çok sorgu yapmak.
  • Sonraki sorgular arasında geçici tablolar gibi veri nesnelerini paylaşma.

Eğer kullanmanız gerekiyorsa pool.getConnection(), aramak sağlamak connection.release()altında benzer bir yapıyı kullanarak:

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

Ben şahsen Promises ve useAsync()kalıbı kullanmayı tercih ederim . async/ İle birleştirilen bu kalıp await, yanlışlıkla release()bağlantıyı unutmayı çok daha zor hale getirir çünkü sözcüksel kapsamınızı otomatik bir çağrıya dönüştürür .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 - sadece bir not - her sorguyu beklemek, pratikte sıralı yerine eşzamanlı olarak çalıştırılabilen birden çok sorgu çalıştırdığınız durumlarda bir anlam ifade etmeyebilir.
random_user_name

1
@cale_b Tuhaf bir şekilde büyülü bir şey yapmıyorsanız, bu sorguları paralel olarak çalıştırmak imkansızdır. Veri bağımlılıkları olan bir işlemde birden çok sorgu çalıştırıyorsanız, birinci sorgunun tamamlandığından emin olana kadar ikinci sorguyu çalıştıramazsınız. Sorgularınız gösterildiği gibi bir işlemi paylaşıyorsa, onlar da bir bağlantıyı paylaşıyor demektir. Her bağlantı bir seferde yalnızca bir sorguyu destekler ( MySQL'de MARS diye bir şey yoktur ).
binki

1
Gerçekte veritabanında birden çok bağımsız işlem gerçekleştiriyorsanız, hiçbir şey sizi usePooledConnectionAsync()ilkini tamamlamadan önce birden çok kez aramaktan alıkoyamaz. Havuzlama ile, geçirdiğiniz awaitişlev içinde sorgu tamamlama dışındaki olaylardan kaçınmak isteyeceğinizi unutmayın - aksi actionAsynctakdirde, bir kilitlenme yaratabilirsiniz (örneğin, bir havuzdan son bağlantıyı elde edin, ardından arayın) havuzu kullanarak veri yüklemeye çalışan başka bir işlev, boşken havuzdan kendi bağlantısını elde etmek için sonsuza kadar bekleyecektir).
binki

1
İlgilendiğiniz için teşekkürler. Bu, anlayışımın zayıf olduğu bir alan olabilir - ancak daha önce (havuzlara geçmeden önce, cevabınızı öncelikle BTW kullanarak) "paralel" olarak çalışan birden çok seçimim vardı (ve sonra sonuçları js mantığımda geri geldikten sonra birleştiriyorum ). Bunun sihirli olduğunu sanmıyorum, ama bir awaitsonrakini sormadan önce BİRİNİ YAPMAMAK iyi bir strateji gibi görünüyordu . Ben artık herhangi bir analiz yapmadım ama (yeni Promises dönen) şeyler yazmış olduğunuz yolu, onu düşünüyorum hala ... paralel çalışan
random_user_name

@cale_b Doğru, bu kalıbın kötü olduğunu söylemiyorum. Birden fazla veri parçası yüklemeniz gerekiyorsa ve bunların bağımsız veya yeterince değişmediği varsayılabilirse, bir grup bağımsız yük başlatmak ve sonra awaitbunları yalnızca sonuçları bir araya getirmek için gerçekten ihtiyaç duyduğunuzda uygulamak, bunu yapmanın bir yoludur. (Bu, gelecekte node.js'yi çökertebilecek yanlış pozitif işlenmemiş söz reddi olaylarına neden olacağından korkuyorum --unhandled-rejections=strict).
binki

14

Bu ambalajı faydalı bulacaksınız :)

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

Gerektirin, şu şekilde kullanın:

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

Bu temel sınıf bağlantısını mysql ile kullanıyorum:

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

Sadece böyle kullan:

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

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

1
Ya sorgu errdoğruysa? Sorguda bazı hatalar olduğunu belirtmek için yine callbackde nullparametresi çağırmamalı mı?
Joe Huang

Evet, yazarsınız, sorgu hatasıyla geri
aramayı balonlaştırmanız gerekir

Güzel bir. Ancak elsebunun gibi bir koşul eklemelisiniz : if (!err) { callback(rows, err); } else { callback(null, err); }aksi takdirde uygulamanız kilitlenebilir. Çünkü connection.on('error', callback2)tüm "hataları" dikkate almaz. Teşekkürler!
Tadej

aynen, bu düzeltmeyi ekledim
Sagi Tsofan

nodejs newbe here: Neden fonksiyon (veri, hata) ve geri arama (veri, hata) var; Gördüğüm tüm nodejs kodunun çoğu ilk parametre olarak hata ve ikinci parametre olarak veri / geri arama olduğunda? örn: geri arama (hata, sonuçlar)
KingAndrew

2

Bir bağlantı ile işiniz bittiğinde, sadece arayın connection.release()ve bağlantı havuza geri dönecek, başka biri tarafından tekrar kullanılmaya hazır olacaktır.

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

Bağlantıyı kapatmak ve havuzdan kaldırmak isterseniz, connection.destroy()bunun yerine kullanın. Havuz, bir dahaki sefere ihtiyaç duyulduğunda yeni bir bağlantı oluşturacaktır.

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


0

Standart mysql.createPool () kullanılarak, bağlantılar havuz tarafından tembel olarak oluşturulur. Havuzu 100 bağlantıya kadar izin verecek şekilde yapılandırır, ancak aynı anda yalnızca 5 kullanırsanız, yalnızca 5 bağlantı yapılacaktır. Ancak, bunu 500 bağlantı için yapılandırırsanız ve 500'ün tümünü kullanırsanız, bunlar boşta olsalar bile işlem boyunca açık kalacaktır!

Bu, MySQL Sunucunuz max_connections 510 ise, MySQL Sunucunuz kapatana kadar (wait_timeout ayarınıza bağlı olarak) veya uygulamanız kapanıncaya kadar sisteminizin yalnızca 10 mySQL bağlantısına sahip olacağı anlamına gelir! Bunları serbest bırakmanın tek yolu, havuz örneği aracılığıyla bağlantıları manuel olarak kapatmak veya havuzu kapatmaktır.

mysql-connection-pool-manager modülü, bu sorunu gidermek ve yüke bağlı olarak bağlantı sayısını otomatik olarak ölçeklendirmek için oluşturulmuştur. Etkin olmayan bağlantılar kapatılır ve herhangi bir etkinlik yoksa, boşta kalan bağlantı havuzları sonunda kapatılır.

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

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


-6

her zaman connection.relase () kullanırım; pool.getconnetion'dan sonra

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
Bağlantıyı sorgulamak için kullanmadan önce bırakmanız gerektiğini düşünmeyin
kwhitley

2
Evet, bu kötü bir haber .... bu sürümden uzaklaştığınız şeylerin eşzamansız doğasının bir yan etkisi. Biraz gecikme yaşarsanız bu sorguyu görmezsiniz. Kalıp ... pool.getConnection (function (err, connection) {// Connection connection.query'yi kullanın ('BAZEN BİR ŞEYİ SEÇİN', function (hata, sonuçlar, alanlar) {// Ve bağlantıyla yapılır. connection.release (); // Sürümden
hpavc
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.