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モジュールのexports部分を書き直して、接続だけを返すようにすると、毎回getConnection()を書く必要がなくなりますか?


2
これを見つけて「connection.queryコードのいたるところにある」と思う人には、おそらくリファクタリングの時間です。提供しているデータベース抽象クラスを構築しselectinsertupdateとのみ使用-などconnection(またはpool)その単一のDBクラス内には...
random_user_name

@random_user_name提案を実装するリンクやコードはありますか?
KingAndrew

@random_user_nameこの場合、トランザクションをどのように管理しますか?各クエリの後に接続を解放した場合はどうなりますか?
ジェフライアン

@JeffRyanは、このdbクラスを拡張する他のクラスを持つことができ、特別なトランザクションを必要とする特定のケースを管理します。しかし、random_user_nameの提案は必ずしもトランザクションに反するものではないと思います...私は一般的に同様のパターンを使用します。基本的なメソッドを提供する基本モデルクラスを作成します。たとえば、挿入メソッドは最初にレコードを挿入するため、トランザクションが必要です。次に、最後に挿入されたIDで選択して、結果を取得します。
lucasreta

回答:


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 2014

@Spockこの例にリンクできますか?Express Promiseは、これまでのところ、作業するのが面倒です。何かが足りないと思います。これまでのところ、var deferred = q.defer()を使用してから解決または拒否することしかできませんが、これは非常に単純なものでは多くのオーバーヘッドのようです。もしそうなら、ありがとう:)
PixMach 2015年

1
pool.query()直接使用することもできます。これはpool.getConnection()-> connection.query()->connection.release()コードフローのショートカットです。
ギャルシャブーディ2018

30

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
+ 1-メモ-すべてのクエリを待つことは、実際には順番にではなく同時に実行できる複数のクエリを実行している場合には意味がない場合があります。
random_user_name

1
@cale_b奇妙な魔法のようなことをしているのでない限り、これらのクエリを並行して実行することは不可能です。データ依存関係のあるトランザクションで複数のクエリを実行している場合、最初のクエリが完了したことを確認するまで、2番目のクエリを実行することはできません。示されているように、クエリがトランザクションを共有している場合、それらは接続も共有しています。各接続は一度に1つのクエリのみをサポートします(MySQLにはMARSのようなものはありません)。
binki

1
実際にデータベースで複数の独立した操作を実行している場合usePooledConnectionAsync()、最初の操作が完了する前に複数回呼び出すことを妨げるものは何もありません。プーリングでは、await渡した関数内でクエリ完了以外のイベントを回避する必要があることに注意してください。そうしないとactionAsync、デッドロックが発生する可能性があります(たとえば、プールから最後の接続を取得してから、プールを使用してデータをロードしようとする別の関数。空の場合、プールから独自の接続を取得しようと永久に待機します)。
binki

1
関与していただきありがとうございます。これは私の理解が弱い領域かもしれませんが、以前は(プールに切り替える前に、主にあなたの答えを使用して)、複数の選択を「並列」で実行していました(そして、結果が戻ってきた後、結果をjsロジックにマージします)。それは魔法のようなことではないと思いますがawait、次のことを求める前に誰もしないのは良い戦略のように思えました。私は今、すべての分析を行っていないが、私は(新しい約束を返す)物事を執筆してきた方法は、私はそれがだと思うまだ...並列に実行されている
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がtrueの場合はどうなりますか?クエリにエラーがあることを示すためにcallbacknullパラメータを使用して呼び出す必要がありますか?
Joe Huang

はい、あなたは書きます、クエリエラーでコールバックをバブルアップする必要があります
Sagi Tsofan 2017

良いですね。ただし、次のelseような条件を追加する必要if (!err) { callback(rows, err); } else { callback(null, err); }があります。そうしないと、アプリケーションがハングする可能性があります。そのためconnection.on('error', callback2)、すべての「エラー」の世話をしません。ありがとう!
タデイ2017

正確に、私はこの修正を追加しました
Sagi Tsofan 2017

nodejs newbe here:なぜ関数(データ、エラー)とコールバック(データ、エラー)があるのですか?私が見たすべてのnodejsコードのほとんどが、最初のパラメーターとしてのエラーと2番目のパラメーターとしてのデータ/コールバックである場合はどうでしょうか。例:コールバック(エラー、結果)
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の設定に応じて)、またはアプリケーションが閉じるまで、システムで使用できるmySQL接続は10個だけです。それらを解放する唯一の方法は、プールインスタンスを介して手動で接続を閉じるか、プールを閉じることです。

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


-6

私は常に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);
        }
    });

8
クエリに使用するに接続を解放する必要があるとは思わないでください
kwhitley 2016

2
はい、これは悪いニュースです....このリリースで回避しているものの非同期性の副作用です。レイテンシーを導入すると、そのクエリは表示されません。パターンは... pool.getConnection(function(err、connection){//接続を使用しますconnection.query( 'SELECT something FROM sometable'、function(error、results、fields){//そして接続で完了します。 connection.release(); //リリース後のエラーを処理します。if
hpavc
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.