node.js + tổng hợp kết nối mysql


85

Tôi đang cố gắng tìm ra cách cấu trúc ứng dụng của mình để sử dụng MySQL một cách hiệu quả nhất. Tôi đang sử dụng mô-đun node-mysql. Các chủ đề khác ở đây được đề xuất sử dụng gộp kết nối vì vậy tôi thiết lập một mô-đun nhỏ mysql.js

var mysql = require('mysql');

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

exports.pool = pool;

Bây giờ bất cứ khi nào tôi muốn truy vấn mysql, tôi yêu cầu mô-đun này và sau đó truy vấn databse

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

Cách tiếp cận này có tốt không? Tôi thực sự không thể tìm thấy quá nhiều ví dụ về việc sử dụng kết nối mysql ngoài một ví dụ rất đơn giản nơi mọi thứ được thực hiện trong tập lệnh app.js chính vì vậy tôi không thực sự biết quy ước / phương pháp hay nhất là gì.

Tôi có nên luôn sử dụng connect.end () sau mỗi truy vấn không? Nếu tôi quên nó ở đâu đó thì sao?

Làm cách nào để viết lại phần xuất của mô-đun mysql của tôi để trả về chỉ một kết nối để tôi không phải viết getConnection () mỗi lần?


2
Đối với những người tìm thấy điều này và nghĩ rằng "Tôi có connection.querytất cả mọi nơi trong mã của mình" - có lẽ đã đến lúc cấu trúc lại. Xây dựng một lớp trừu tượng cơ sở dữ liệu cung cấp select, insert, update, vv - và chỉ sử dụng connection(hoặc pool) trong đó lớp db duy nhất ...
random_user_name

@random_user_name bạn có bất kỳ liên kết hoặc mã nào triển khai đề xuất của mình không?
KingAndrew

@random_user_name Bạn sẽ quản lý các giao dịch trong trường hợp này như thế nào? Nếu bạn giải phóng kết nối sau mỗi truy vấn?
Jeff Ryan

@JeffRyan bạn có thể có các lớp khác mở rộng lớp db này, trong đó bạn quản lý các trường hợp cụ thể yêu cầu giao dịch bất thường. Nhưng tôi nghĩ rằng đề xuất của random_user_name không nhất thiết phải chống lại các giao dịch ... Tôi thường sử dụng một mẫu tương tự, trong đó tôi tạo một lớp mô hình cơ sở cung cấp các phương thức cơ bản và phương thức chèn ví dụ yêu cầu các giao dịch, vì nó lần đầu tiên chèn một bản ghi và sau đó chọn theo ID được chèn lần cuối để lấy kết quả.
lucasreta

Câu trả lời:


68

Đó là một cách tiếp cận tốt.

Nếu bạn chỉ muốn có kết nối, hãy thêm mã sau vào mô-đun của bạn, nơi có nhóm:

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

module.exports = getConnection;

Bạn vẫn phải viết getConnection mỗi lần. Nhưng bạn có thể lưu kết nối trong mô-đun lần đầu tiên bạn nhận được.

Đừng quên ngắt kết nối khi bạn sử dụng xong:

connection.release();

18
Chỉ cần một cái nhìn lên. Đó là connection.release();bây giờ, cho các hồ bơi.
sdanzig

Đúng. Tôi đã thay đổi nó.
Klaasvaak

Ngoài ra, nếu tôi có thể, tôi sẽ đề nghị sử dụng một lời hứa thay vì gọi lại, nhưng đó chỉ là một sở thích ... giải pháp tuyệt vời dù sao
Spock

@Spock bạn có thể liên kết đến một ví dụ về điều này không? Những lời hứa cấp tốc khá khó chịu khi làm việc cho đến nay, tôi nghĩ rằng tôi đang thiếu một cái gì đó. Cho đến nay tôi chỉ có thể sử dụng var deferred = q.defer () và sau đó giải quyết hoặc từ chối, nhưng điều đó có vẻ như rất nhiều chi phí cho một thứ quá đơn giản. Nếu vậy, nhờ :)
PixMach

1
Bạn cũng có thể sử dụng pool.query()trực tiếp. Đây là một phím tắt cho dòng mã pool.getConnection()-> connection.query()-> connection.release().
Gal Shaboodi

27

Bạn nên tránh sử dụng pool.getConnection()nếu bạn có thể. Nếu bạn gọi pool.getConnection(), bạn phải gọi connection.release()khi bạn sử dụng xong kết nối. Nếu không, ứng dụng của bạn sẽ gặp khó khăn khi chờ đợi mãi mãi các kết nối được trả về nhóm sau khi bạn đạt đến giới hạn kết nối.

Đối với các truy vấn đơn giản, bạn có thể sử dụng pool.query(). Cách viết tắt này sẽ tự động gọi connection.release()cho bạn — ngay cả trong điều kiện lỗi.

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

Tuy nhiên, trong một số trường hợp bạn phải sử dụng pool.getConnection(). Những trường hợp này bao gồm:

  • Thực hiện nhiều truy vấn trong một giao dịch.
  • Chia sẻ các đối tượng dữ liệu như bảng tạm thời giữa các truy vấn tiếp theo.

Nếu bạn phải sử dụng pool.getConnection(), hãy đảm bảo bạn gọi connection.release()bằng mẫu tương tự như bên dưới:

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

Cá nhân tôi thích sử dụng Promises và useAsync()mẫu. Mẫu này kết hợp với async/ awaitkhiến việc vô tình quên release()kết nối sẽ khó hơn rất nhiều vì nó biến phạm vi từ vựng của bạn thành một cuộc gọi tự động tới .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 - chỉ là một lưu ý - việc chờ đợi mọi truy vấn có thể không có ý nghĩa trong trường hợp bạn đang chạy nhiều truy vấn mà trên thực tế có thể được chạy đồng thời thay vì tuần tự.
random_user_name

@cale_b Trừ khi bạn đang làm điều gì đó kỳ lạ, thì việc chạy các truy vấn này song song là điều không thể. Nếu bạn đang chạy nhiều truy vấn trong một giao dịch có phụ thuộc dữ liệu, bạn không thể chạy truy vấn thứ hai cho đến khi bạn chắc chắn rằng truy vấn đầu tiên đã hoàn thành. Nếu các truy vấn của bạn đang chia sẻ một giao dịch, như đã chứng minh, chúng cũng đang chia sẻ một kết nối. Mỗi kết nối chỉ hỗ trợ một truy vấn tại một thời điểm (không có cái gọi là MARS trong MySQL).
binki

1
Nếu trên thực tế, bạn đang thực hiện nhiều thao tác độc lập trong cơ sở dữ liệu, thì không có gì ngăn bạn gọi usePooledConnectionAsync()nhiều lần trước khi hoàn thành thao tác đầu tiên. Lưu ý rằng, với tính năng gộp, bạn sẽ muốn đảm bảo rằng bạn tránh nhập awaitcác sự kiện ngoài việc hoàn thành truy vấn trong hàm mà bạn truyền vào — actionAsyncnếu không, bạn có thể tạo ra một deadlock (ví dụ: lấy kết nối cuối cùng từ một nhóm, sau đó gọi một chức năng khác cố gắng tải dữ liệu bằng cách sử dụng nhóm sẽ đợi mãi mãi để cố gắng lấy kết nối của riêng nó từ nhóm khi trống).
binki

1
Cảm ơn vì đã tham gia. Đây có thể là lĩnh vực mà tôi chưa hiểu rõ - nhưng trước đây (trước khi chuyển sang nhóm, chủ yếu sử dụng câu trả lời của bạn, BTW), tôi đã có nhiều lựa chọn chạy "song song" (và sau đó tôi hợp nhất các kết quả theo logic js của mình sau khi chúng quay trở lại ). Tôi không nghĩ đó là điều kỳ diệu, nhưng nó có vẻ là một chiến lược tốt để KHÔNG chọn awaitmột trước khi yêu cầu tiếp theo. Tôi đã không làm bất kỳ phân tích bây giờ, nhưng cách tôi đã là tác giả của sự vật (trở về Promises mới), tôi nghĩ rằng nó vẫn chạy song song ...
random_user_name

@cale_b Đúng vậy, tôi không nói rằng mô hình đó là xấu. Nếu bạn cần tải nhiều phần dữ liệu và có thể giả định rằng chúng độc lập hoặc không thay đổi đủ, khởi chạy một loạt các tải độc lập và sau đó chỉ nhập awaitchúng khi bạn thực sự cần chúng để tổng hợp các kết quả với nhau là một cách để làm điều đó (mặc dù tôi sợ rằng điều đó sẽ dẫn đến các sự kiện từ chối lời hứa không được xử lý dương tính giả có thể làm hỏng node.js trong tương lai với --unhandled-rejections=strict).
binki

14

Bạn sẽ thấy trình bao bọc này hữu ích :)

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

Yêu cầu nó, sử dụng nó như thế này:

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

Tôi đang sử dụng kết nối lớp cơ sở này với 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;

Chỉ cần sử dụng nó như vậy:

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

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

1
Điều gì sẽ xảy ra nếu truy vấn của errlà đúng? không phải nó vẫn gọi callbackvới nulltham số để chỉ ra rằng có một số lỗi trong truy vấn?
Joe Huang

Vâng, bạn viết, cần phải bong bóng lên callback với các lỗi truy vấn
Sagi Tsofan

Tốt lắm. Nhưng bạn nên thêm một elseđiều kiện như sau: if (!err) { callback(rows, err); } else { callback(null, err); }nếu không ứng dụng của bạn có thể bị treo. Bởi vì connection.on('error', callback2)sẽ không chăm sóc tất cả các "lỗi". Cảm ơn!
Tadej

chính xác, tôi đã thêm bản sửa lỗi này
Sagi Tsofan

nodejs newbe tại đây: Tại sao bạn có hàm (dữ liệu, lỗi) và gọi lại (dữ liệu, lỗi); khi hầu hết tất cả mã nodejs mà tôi đã thấy bị lỗi ở dạng tham số đầu tiên và dữ liệu / lệnh gọi lại là tham số thứ hai? ví dụ: gọi lại (lỗi, kết quả)
KingAndrew

2

Khi bạn hoàn tất việc kết nối, chỉ cần gọi connection.release()và kết nối sẽ trở lại chung, sẵn sàng để người khác sử dụng lại.

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

Nếu bạn muốn đóng kết nối và xóa nó khỏi nhóm, hãy sử dụng connection.destroy()thay thế. Nhóm sẽ tạo kết nối mới vào lần tiếp theo khi cần kết nối.

Nguồn : https://github.com/mysqljs/mysql


0

Sử dụng tiêu chuẩn mysql.createPool (), các kết nối được tạo một cách lười biếng bởi nhóm. Nếu bạn định cấu hình nhóm để cho phép tối đa 100 kết nối, nhưng chỉ sử dụng đồng thời 5 kết nối, thì sẽ chỉ có 5 kết nối được thực hiện. Tuy nhiên, nếu bạn định cấu hình nó cho 500 kết nối và sử dụng tất cả 500, chúng sẽ vẫn mở trong thời gian của quá trình, ngay cả khi chúng không hoạt động!

Điều này có nghĩa là nếu MySQL Server max_connections của bạn là 510, hệ thống của bạn sẽ chỉ có 10 kết nối mySQL cho đến khi MySQL Server của bạn đóng chúng (tùy thuộc vào những gì bạn đã đặt wait_timeout) hoặc ứng dụng của bạn đóng! Cách duy nhất để giải phóng chúng là đóng các kết nối theo cách thủ công thông qua phiên bản nhóm hoặc đóng nhóm.

Mô-đun mysql-connection-pool-manager đã được tạo để khắc phục sự cố này và tự động chia tỷ lệ số lượng kết nối phụ thuộc vào tải. Các kết nối không hoạt động bị đóng và các nhóm kết nối không hoạt động cuối cùng sẽ bị đóng nếu không có bất kỳ hoạt động nào.

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

Tham khảo: https://www.npmjs.com/package/mysql-connection-pool-manager


-5

tôi luôn sử dụng connect.relase (); sau khi pool.getconnetion like

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

7
Đừng nghĩ bạn nên giải phóng các kết nối trước khi bạn sử dụng nó để truy vấn
kwhitley

1
Vâng, đây là một tin xấu .... nó là một tác dụng phụ của bản chất không đồng bộ của những thứ mà bạn đang nhận được với bản phát hành này. Nếu bạn đưa ra một số độ trễ, bạn sẽ không thấy truy vấn đó. Mẫu là ... pool.getConnection (function (err, connection) {// Sử dụng kết nối connection.query ('CHỌN thứ gì đó TỪ sometable', function (error, results, fields) {// Và thực hiện với kết nối. connect.release
hpavc
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.