【问题标题】:node.js + mysql connection poolingnode.js + mysql 连接池
【发布时间】:2013-09-01 00:52:42
【问题描述】:

我试图弄清楚如何构建我的应用程序以最有效地使用 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);
         })
     })
}

这是好方法吗?我真的找不到太多使用 mysql 连接的例子,除了一个非常简单的例子,所有的事情都在 app.js 主脚本中完成,所以我真的不知道约定/最佳实践是什么。

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

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

【问题讨论】:

  • 对于那些发现这个并认为“我的代码中到处都是connection.query”的人——可能是重构的时候了。构建一个提供selectinsertupdate 等的数据库抽象类 - 并且仅在该单个数据库类中使用connection(或pool)...
  • @random_user_name 你有任何链接或代码可以实现你的建议吗?
  • @random_user_name 在这种情况下您将如何管理事务?如果在每次查询后释放连接?
  • @JeffRyan 您可以拥有扩展此数据库类的其他类,您可以在其中管理需要特殊事务的特定情况。但我认为random_user_name的建议不一定反对事务......我通常使用类似的模式,在其中我创建一个提供基本方法的基本模型类,例如插入方法需要事务,因为它首先插入一条记录然后按最后插入的 ID 进行选择以检索结果。

标签: mysql node.js


【解决方案1】:

这是一个很好的方法。

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

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

module.exports = getConnection;

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

用完别忘了结束连接:

connection.release();

【讨论】:

  • 请注意。现在是connection.release();,用于游泳池。
  • 确实如此。我改了。
  • 另外,如果可以的话,我会建议使用 Promise 而不是回调,但这只是一个偏好......不过还是很好的解决方案
  • @Spock 你能链接到这个例子吗?到目前为止,表达承诺有点烦人,我想我错过了一些东西。到目前为止,我只能使用 var deferred = q.defer() 然后解析或拒绝,但这对于如此简单的事情来说似乎有很多开销。如果是的话,谢谢:)
  • 也可以直接使用pool.query()。这是pool.getConnection() -> connection.query() -> connection.release() 代码流的快捷方式。
【解决方案2】:

如果可以的话,你应该避免使用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 - 只是一个注释 - 在您运行多个查询的情况下等待每个查询可能没有意义,实际上这些查询可以同时运行而不是顺序运行。
  • @cale_b 除非您正在做一些非常神奇的事情,否则并行运行这些查询是不可能的。如果您在具有数据依赖关系的事务中运行多个查询,则在确定第一个查询已完成之前,您不能运行第二个查询。如果您的查询共享事务,如所示,它们也共享连接。每个连接一次只支持一个查询(在 MySQL 中没有 MARS 这样的东西)。
  • 如果您实际上在数据库中执行多个独立操作,那么没有什么能阻止您在第一个操作完成之前多次调用usePooledConnectionAsync()。请注意,使用池化时,您需要确保避免在作为actionAsync 传递的函数中的查询完成以外的awaiting 事件——否则,您最终可能会创建死锁(例如,获取最后一个连接从池中,然后调用另一个函数,该函数尝试使用池加载数据,该池将永远等待尝试从池中获取自己的连接(当空时)。
  • 感谢参与。这可能是我理解较弱的一个领域-但是,以前(在切换到池之前,主要使用您的答案,顺便说一句)我有多个选择以“并行”运行(然后我在返回后将结果合并到我的 js 逻辑中)。我不认为这很神奇,但在要求下一个之前不要 await 似乎是一个好策略。我现在还没有做任何分析,但是我创作事物的方式(返回新的 Promise),我认为它仍然并行运行......
  • @cale_b 是的,我并不是说这种模式不好。如果您需要加载多条数据并且可以假设它们是独立的或足够不变的,则启动一堆独立的加载,然后仅在您真正需要它们组合结果时awaiting它们是一种方法去做(虽然我害怕这会导致误报未处理的承诺拒绝事件,这可能会在将来使用--unhandled-rejections=strict 使 node.js 崩溃)。
【解决方案3】:

你会发现这个包装器很有用:)

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

【讨论】:

    【解决方案4】:

    我正在使用这个基类与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);
    });
    

    【讨论】:

    • 如果查询的err 为真怎么办?它不应该仍然使用null 参数调用callback 来指示查询中有错误吗?
    • 是的,你写,需要用查询错误冒泡回调
    • 不错的一个。但是您应该像这样添加else 条件:if (!err) { callback(rows, err); } else { callback(null, err); } 否则您的应用程序可能会挂起。因为connection.on('error', callback2) 不会处理所有“错误”。谢谢!
    • 没错,我添加了这个修复
    • nodejs newbe here:为什么你有函数(数据,错误)和回调(数据,错误);当我看到的所有nodejs代码中的大多数都是错误作为第一个参数而数据/回调作为第二个参数时?例如:回调(错误,结果)
    【解决方案5】:

    连接完成后,只需调用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

    【讨论】:

      【解决方案6】:

      你可以像我一样使用这种格式

          const mysql = require('mysql');
          const { HOST, USERNAME, PASSWORD, DBNAME, PORT } = process.env;
          console.log();
          const conn = mysql.createPool({
              host: HOST,
              user: USERNAME,
              password: PASSWORD,
              database: DBNAME
          }, { debug: true });
          
          conn.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
              if (error) throw error;
              console.log('Db is connected - The solution is: ', results[0].solution);
          });
          
          
          module.exports = conn;
      

      【讨论】:

        【解决方案7】:

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

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

        创建 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

        【讨论】:

          【解决方案8】:

          我总是使用 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);
                  }
              });
          

          【讨论】:

          • 不要认为您应该在使用它进行查询之前释放连接
          • 是的,这是个坏消息....这是您在此版本中摆脱的异步性质的副作用。如果您引入一些延迟,您将看不到该查询。模式是 ... pool.getConnection(function(err, connection) { // 使用连接 connection.query('SELECT something FROM sometable', function (error, results, fields) { // 完成连接。 connection.release(); // 释放后处理错误 if(error) throw error; npmjs.com/package/mysql#pooling-connections
          猜你喜欢
          • 2018-05-28
          • 2013-10-01
          • 2018-07-15
          • 2016-06-25
          • 1970-01-01
          • 2014-03-25
          • 1970-01-01
          • 2013-08-20
          • 1970-01-01
          相关资源
          最近更新 更多