node.js + mysql连接池


85

我试图弄清楚如何构造我的应用程序以最有效地使用MySQL。我正在使用node-mysql模块。这里的其他线程建议使用连接池,因此我设置了一个小模块mysql.js

var mysql = require('mysql');

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

exports.pool = pool;

现在,每当我要查询mysql时,我都需要此模块,然后查询数据库

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

这是好方法吗?除了非常简单的示例(其中所有操作均在主app.js脚本中完成)之外,我真的找不到太多的使用mysql连接的示例,因此我真的不知道约定/最佳实践是什么。

我应该在每次查询后始终使用connection.end()吗?如果我忘记了某个地方怎么办?

如何重写我的mysql模块的导出部分以仅返回连接,所以我不必每次都写getConnection()?


2
对于那些发现这一点并认为“connection.query我的代码中到处都是”的人来说,可能是时候重构了。建立一个数据库抽象类的提议的selectinsertupdate,等等-只有使用connection(或pool)是单DB类内...
random_user_name

@random_user_name您是否有任何链接或代码可以实现您的建议?
KingAndrew

@random_user_name在这种情况下,您将如何管理事务?是否在每次查询后释放连接?
Jeff Ryan

@JeffRyan您可以具有其他类来扩展此db类,在该类中您可以管理需要特殊事务的特殊情况。但是我认为random_user_name的建议不一定针对事务...我通常使用类似的模式,在该模式中,我创建一个提供基本方法的基模型类,而插入方法例如需要事务,因为它首先插入一条记录然后按最后插入的ID进行选择以检索结果。
lucasreta

Answers:


68

这是一个好方法。

如果只想建立连接,请将以下代码添加到池所在的模块中:

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

module.exports = getConnection;

您仍然必须每次都写getConnection。但是,您可以在第一次获得连接时将其保存在模块中。

使用完后,不要忘记终止连接:

connection.release();

18
只是抬起头。这是connection.release();现在,对于池。
sdanzig 2013年

确实如此。我改了
Klaasvaak 2013年

另外,如果可以的话,我建议您使用Promise而不是回调,但这只是一个首选项...很棒的解决方案
Spock

您可以链接到@Spock的示例吗?到目前为止,使用Express Prom还是很烦人的,我想我错过了一些东西。到目前为止,我只能使用var deferred = q.defer()然后解析或拒绝,但是对于如此简单的操作而言,这似乎有很多开销。如果是这样,谢谢:)
PixMach

1
您也可以pool.query()直接使用。这是pool.getConnection()-> connection.query()->connection.release()代码流的快捷方式。
Gal Shaboodi

27

pool.getConnection()如果可以,应避免使用。如果您致电pool.getConnection(),则必须connection.release()在使用完连接后致电。否则,一旦达到连接限制,您的应用程序将永远陷入等待连接返回到池的状态。

对于简单查询,您可以使用pool.query()connection.release()即使在错误情况下,该速记也会自动为您服务。

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

但是,在某些情况下,您必须使用pool.getConnection()。这些情况包括:

  • 在一个事务中进行多个查询。
  • 在后续查询之间共享数据对象,例如临时表。

如果必须使用pool.getConnection(),请确保connection.release()使用类似于以下的模式进行调用:

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

我个人更喜欢使用Promises和useAsync()模式。与async/结合使用的这种模式await使意外忘记release()连接变得更加困难,因为它将词法作用域转换为对以下内容的自动调用.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-只是一个注释-在您正在运行多个查询(实际上可以同时而非顺序运行)的情况下,等待每个查询可能没有意义。
random_user_name

@cale_b除非您执行不可思议的神奇操作,否则并行运行这些查询是不可能的。如果您在具有数据依赖性的事务中运行多个查询,则在确定第一个查询完成之前,无法运行第二个查询。如所示,如果您的查询正在共享事务,那么它们也在共享连接。每个连接一次仅支持一个查询(MySQL中没有诸如MARS之类的东西)。
binki

1
如果实际上您正在数据库中执行多个独立的操作,则没有什么可以阻止您usePooledConnectionAsync()在第一个操作完成之前多次调用。请注意,使用缓冲池时,您需要确保避免await在传递的函数中发生查询完成以外的事件,actionAsync否则,您可能最终会产生死锁(例如,从缓冲池中获取最后一个连接,然后调用另一个尝试使用池加载数据的函数,当该函数为空时,它将永远等待从池中获取自己的连接)。
binki

1
感谢您的参与。这可能是我的理解薄弱的一个领域-但是,以前(在切换到池之前,首先使用您的答案,顺便说一句)我在“并行”中运行了多个选择(然后在结果返回后将其合并到我的js逻辑中) )。我认为这不是神奇的法宝,但是await在要求下一个之前,最好不要选择一个这样的策略。我现在还没有做任何分析,但是我编写事情的方式(返回新的Promises),我认为它仍在并行运行……
random_user_name

@cale_b是的,我并不是说模式不好。如果您需要加载多个数据,并且可以假定它们是独立的或足够不变的,则启动一堆独立的加载,然后仅await在实际需要它们组合结果时才进行加载(尽管我担心这会导致误报未处理的诺言拒绝事件,将来可能会导致崩溃node.js --unhandled-rejections=strict)。
binki

14

您会发现此包装有用:)

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

需要它,像这样使用它:

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

我正在与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;

像这样使用它:

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

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

1
如果查询err为真该怎么办?它是否还应该callback使用null参数调用以指示查询中存在一些错误?
Joe Huang

是的,您编写了,需要用查询错误使回调冒泡
Sagi Tsofan

好东西。但是您应该添加这样的else条件:if (!err) { callback(rows, err); } else { callback(null, err); }否则您的应用程序可能会挂起。因为connection.on('error', callback2)不会照顾所有的“错误”。谢谢!
Tadej

确切地说,我添加了此修复程序
Sagi Tsofan '17

nodejs newbehere:为什么要有函数(数据,错误)和回调(数据,错误);当我见过的大多数nodejs代码中,错误是第一个参数,数据/回调是第二个参数时?例如:回调(错误,结果)
KingAndrew

2

完成连接后,只需调用即可connection.release(),连接将返回到池中,可供其他人再次使用。

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

如果要关闭连接并将其从池中删除,请connection.destroy()改用。下次需要该池时,该池将创建一个新连接。

来源https : //github.com/mysqljs/mysql


0

使用标准的mysql.createPool(),连接是由池延迟创建的。如果将池配置为最多允许100个连接,但仅同时使用5个,则仅建立5个连接。但是,如果将其配置为500个连接并使用所有500个连接,即使它们处于空闲状态,它们也将在此过程中保持打开状态!

这意味着,如果您的MySQL Server max_connections为510,则在MySQL Server关闭它们之前(取决于您将wait_timeout设置为什么)或应用程序关闭,系统将只有10个mySQL连接可用。释放它们的唯一方法是通过池实例手动关闭连接或关闭池。

创建mysql-connection-pool-manager模块来解决此问题,并根据负载自动调整连接数。如果没有任何活动,则关闭不活动的连接,并最终关闭空闲的连接池。

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

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


-5

我总是使用connection.relase(); 在pool.getconnetion之后

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
不要以为使用它进行查询之前就应该释放该连接
kwhitley

1
是的,这是个坏消息……这是您在本发行版中无法使用的事物的异步性质的副作用。如果引入一些延迟,则不会看到该查询。模式是... pool.getConnection(function(err,connection){//使用连接connection.query('从某表中选择内容',function(error,result,field)){//完成连接。 connection.release(); //处理发布后的错误if(错误)抛出错误; npmjs.com/package/mysql#pooling-connections
hpavc
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.