Node.js + MySQL 连接池

104

我正在尝试找出如何最有效地构建我的应用程序来使用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 时,我需要使用这个模块,然后查询数据库。
现在,每当我想查询 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模块的exports部分,以返回一个连接,这样我就不必每次都写getConnection()了?


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

78

这是一个不错的方法。

如果你只想获得一个连接,请将以下代码添加到包含池的模块中:

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

module.exports = getConnection;

每次仍需编写getConnection。但第一次获取连接时,您可以将连接保存在模块中。

在使用完连接后,不要忘记关闭它:

connection.release();

21
提醒一下,现在对于连接池来说,释放连接的方式是使用 connection.release(); - sdanzig
没错,我改了它。 - Klaasvaak
1
非常抱歉回复晚了。这是我使用 Promises 获取连接的方法,虽然有些麻烦,但使用 Promises 更加美妙,我讨厌回调 :) .. code var getConnection = function() {var deferred = Q.defer(); pool.getConnection(function(err, connection) { if(err) { deferred.reject(err); } else { deferred.resolve(connection); } }); return deferred.promise;}; - Spock
1
但是您可以在第一次获取连接时将其保存在模块中。 在使用完连接后不要忘记结束它。这两个语句似乎矛盾。释放连接是一个好的实践吗?还是将其保存在每个模块内? - SSH This
3
您也可以直接使用pool.query()。这是pool.getConnection() -> connection.query() -> connection.release()代码流程的快捷方式。 - Gal Shaboodi
显示剩余5条评论

43

如果可以的话,应该避免使用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);
                  });
                }
              });
            }
          });
        }
      });
    }
  });
}

我个人更喜欢使用PromiseuseAsync()模式。这种模式结合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
+1 - 只是一条注释 - 在运行多个可以同时运行而不是顺序运行的查询的情况下,等待每个查询可能没有意义。 - random_user_name
1
@cale_b 除非你在做一些奇怪的魔法,否则并行运行这些查询是不可能的。如果你在一个具有数据依赖关系的事务中运行多个查询,你不能运行第二个查询,直到你确定第一个查询已经完成。如果你的查询共享一个事务,就像演示的那样,它们也共享一个连接。每个连接一次只支持一个查询(MySQL 中没有 MARS 这样的东西)。 - binki
1
如果您实际上正在数据库中执行多个独立操作,那么在第一个操作完成之前多次调用usePooledConnectionAsync()是没有问题的。请注意,在使用池时,您需要确保避免在作为actionAsync传递的函数内等待除查询完成以外的其他事件 - 否则,您可能会创建死锁(例如,从池中获取最后一个连接,然后调用另一个尝试使用池加载数据的函数,当池为空时将永远等待尝试获取自己的连接)。 - binki
1
感谢您的参与。这可能是我理解不足的领域 - 但是,在转换为池之前(顺便说一下,主要使用您的答案),我有多个选择在“并行”运行(然后在它们返回后在我的js逻辑中合并结果)。我认为这并不神奇,但似乎是一个很好的策略,可以在请求下一个之前不等待一个。我现在没有进行任何分析,但是按照我编写的方式(返回新的Promise),我认为它仍在并行运行... - random_user_name
@cale_b 没错,我并不是说这种模式是不好的。如果你需要加载多个数据片段,并且可以假定它们是相互独立或足够稳定,那么启动一堆独立的加载操作,然后只有在实际需要将结果组合在一起时才 await 它们是一种方法(虽然我担心这样会导致假阳性未处理的 Promise 拒绝事件,这可能会在 --unhandled-rejections=strict 的情况下崩溃 node.js)。 - binki
当然,每个负载都将来自池中的不同连接。如果您没有注意到,在我的回答中,我描述了多个查询需要共享同一个连接的情况。例如,如果您在一个语句中创建了一个临时表,在另一个语句中插入行,并在第三个语句中使用该临时表,则需要多次使用单个连接而不能使用pool.query()。此外,这些特定的查询将必须按顺序运行。你明白吗? - binki

16

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

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

14

我正在使用这个基类连接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 是 true,那么它不应该仍然调用 callback 并传递 null 参数来指示查询中存在某些错误吗? - Joe Huang
是的,你需要编写代码,将查询错误的回调函数向上冒泡。 - Sagi Tsofan
不错。但你应该添加一个 else 条件,像这样:if (!err) { callback(rows, err); } else { callback(null, err); } 否则你的应用程序可能会挂起。因为 connection.on('error', callback2) 无法处理所有“错误”。谢谢! - tedi
确切地说,我添加了这个修复。 - Sagi Tsofan
1
Node.js新手:为什么有些函数的参数是(data, error),而有些是callback(data, error)?因为我看到的大部分Node.js代码都是将error作为第一个参数,然后是data/callback。例如:callback(error, results)。 - 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

1
你可以像我一样使用这种格式。
    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;

0
使用标准的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


-7

我总是在使用pool.getconnection之后使用connection.release();

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

9
在查询之前,不要认为你应该释放连接。 - kwhitley
2
是的,这是个坏消息……这是异步性质的副作用,你正在通过这个版本逃避它。如果你引入一些延迟,你就不会看到那个查询了。模式是...pool.getConnection(function(err, connection) { // 使用连接 connection.query('SELECT something FROM sometable', function (error, results, fields) { // 连接完成。 connection.release(); // 释放后处理错误。 if (error) throw error;https://www.npmjs.com/package/mysql#pooling-connections - hpavc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接