การรวมการเชื่อมต่อ 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);
         })
     })
}

แนวทางนี้ดีไหม ฉันไม่พบตัวอย่างของการใช้การเชื่อมต่อ mysql มากเกินไปนอกจากตัวอย่างที่เรียบง่ายมากซึ่งทุกอย่างเสร็จสิ้นในสคริปต์ app.js หลักดังนั้นฉันจึงไม่รู้ว่าหลักการปฏิบัติ / แนวทางปฏิบัติที่ดีที่สุดคืออะไร

ฉันควรใช้ connection.end () หลังแบบสอบถามแต่ละครั้งหรือไม่ จะเกิดอะไรขึ้นถ้าฉันลืมมันไว้ที่ไหนสักแห่ง?

วิธีการเขียนส่วนส่งออกของโมดูล mysql ของฉันใหม่เพื่อส่งคืนเฉพาะการเชื่อมต่อดังนั้นฉันจึงไม่ต้องเขียน getConnection () ทุกครั้ง


2
สำหรับผู้ที่พบสิ่งนี้และคิดว่า "ฉันมีconnection.queryทุกที่ในรหัสของฉัน" - อาจถึงเวลาที่ต้องปรับโครงสร้างใหม่ สร้างฐานข้อมูลระดับนามธรรมว่าข้อเสนอselect, insert, updateฯลฯ - และใช้เฉพาะconnection(หรือpool) ภายในที่ระดับฐานข้อมูลเดียว ...
random_user_name

@random_user_name คุณมีลิงก์หรือโค้ดที่ใช้กับคำแนะนำของคุณหรือไม่
KingAndrew

@random_user_name คุณจะจัดการธุรกรรมในกรณีนี้อย่างไร? หากคุณปล่อยการเชื่อมต่อหลังจากการสืบค้นแต่ละครั้ง?
Jeff Ryan

@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

นั่นคือเรื่องจริง ฉันเปลี่ยนมัน
Klaasvaak

นอกจากนี้ถ้าฉันทำได้ฉันขอแนะนำให้ใช้คำสัญญาแทนการโทรกลับ แต่นั่นเป็นเพียงความชอบเท่านั้น ... วิธีแก้ปัญหาที่ยอดเยี่ยม
สป็อค

@Spock ช่วยลิงค์ไปยังตัวอย่างนี้ได้ไหม สัญญาด่วนเป็นเรื่องที่น่ารำคาญในการทำงานจนถึงตอนนี้ฉันคิดว่าฉันขาดอะไร จนถึงตอนนี้ฉันสามารถใช้ได้เฉพาะ var deferred = q.defer () จากนั้นแก้ไขหรือปฏิเสธ แต่ดูเหมือนว่าจะมีค่าใช้จ่ายมากสำหรับบางสิ่งที่เรียบง่าย ถ้าเป็นเช่นนั้นขอบคุณ :)
PixMach

1
คุณยังสามารถใช้pool.query()โดยตรง นี่คือทางลัดสำหรับpool.getConnection()-> connection.query()-> connection.release()โค้ดโฟลว์
กัลชาบูดี

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 เว้นเสียแต่ว่าคุณจะทำอะไรบางอย่างที่มีมนต์ขลังแปลก ๆ การเรียกใช้คำค้นหาเหล่านี้แบบคู่ขนานเป็นไปไม่ได้ หากคุณกำลังเรียกใช้หลายคิวรีในธุรกรรมที่มีการอ้างอิงข้อมูลคุณจะไม่สามารถรันคิวรีที่สองได้จนกว่าคุณจะแน่ใจว่าคิวรีแรกเสร็จสมบูรณ์ หากคำถามของคุณกำลังแบ่งปันธุรกรรมดังที่แสดงให้เห็นพวกเขากำลังแบ่งปันการเชื่อมต่อด้วย การเชื่อมต่อแต่ละครั้งรองรับการสืบค้นเพียงครั้งเดียว (ไม่มีสิ่งที่เรียกว่าMARSใน MySQL)
binki

1
หากในความเป็นจริงคุณกำลังดำเนินการหลายอย่างที่เป็นอิสระในฐานข้อมูลไม่มีสิ่งใดที่จะหยุดคุณจากการโทรusePooledConnectionAsync()หลายครั้งก่อนที่จะเสร็จสิ้นขั้นตอนแรก โปรดทราบว่าด้วยการรวมกลุ่มคุณจะต้องแน่ใจว่าคุณหลีกเลี่ยงawaitเหตุการณ์ที่นอกเหนือไปจากการเติมคำค้นหาภายในฟังก์ชันที่คุณส่งผ่านไปactionAsyncมิฉะนั้นคุณอาจจบลงด้วยการสร้างการชะงักงัน (เช่นรับการเชื่อมต่อครั้งสุดท้ายจากพูลจากนั้นโทร ฟังก์ชันอื่นที่พยายามโหลดข้อมูลโดยใช้พูลซึ่งจะรอตลอดไปเพื่อพยายามรับการเชื่อมต่อของตัวเองจากพูลเมื่อว่างเปล่า)
binki

1
ขอบคุณสำหรับการมีส่วนร่วม นี่อาจเป็นพื้นที่ที่ความเข้าใจของฉันอ่อนแอ - แต่ก่อนหน้านี้ (ก่อนที่จะเปลี่ยนไปใช้พูลโดยใช้คำตอบของคุณเป็นหลัก BTW) ฉันมีการเลือกหลายรายการใน "ขนาน" (จากนั้นฉันรวมผลลัพธ์ในตรรกะ 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เป็นจริง ไม่ควรเรียกcallbackด้วยnullพารามิเตอร์เพื่อระบุว่ามีข้อผิดพลาดในแบบสอบถามหรือไม่?
Joe Huang

ใช่คุณเขียนต้องการแจ้งให้เรียกกลับด้วยข้อผิดพลาดในการค้นหา
Sagi Tsofan

ทำได้ดีนี่. แต่คุณควรเพิ่มelseเงื่อนไขเช่นนี้if (!err) { callback(rows, err); } else { callback(null, err); }มิฉะนั้นแอปพลิเคชันของคุณอาจค้าง เพราะconnection.on('error', callback2)จะไม่ดูแล "ข้อผิดพลาด" ทั้งหมด ขอบคุณ!
ธ เดช

แน่นอนฉันได้เพิ่มการแก้ไขนี้
Sagi Tsofan

nodejs newbe ที่นี่ทำไมคุณถึงมีฟังก์ชัน (ข้อมูลข้อผิดพลาด) และการเรียกกลับ (ข้อมูลข้อผิดพลาด); เมื่อโค้ด nodejs ส่วนใหญ่ที่ฉันเห็นมีข้อผิดพลาดเป็นพารามิเตอร์แรกและ data / callback เป็นพารามิเตอร์ตัวที่สอง? เช่นโทรกลับ (ข้อผิดพลาดผลลัพธ์)
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 max_connections ของคุณคือ 510 ระบบของคุณจะมีการเชื่อมต่อ mySQL เพียง 10 รายการจนกว่าเซิร์ฟเวอร์ 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


-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

2
ใช่นี่เป็นข่าวร้าย .... ซึ่งเป็นผลข้างเคียงของลักษณะการไม่ซิงค์ของสิ่งต่างๆที่คุณได้รับจากรุ่นนี้ หากคุณแนะนำเวลาในการตอบสนองคุณจะไม่เห็นข้อความค้นหานั้น รูปแบบคือ ... pool.getConnection (ฟังก์ชัน (err, การเชื่อมต่อ) {// ใช้การเชื่อมต่อ connection.query ('เลือกบางสิ่งจากบางอย่าง', ฟังก์ชัน (ข้อผิดพลาด, ผลลัพธ์, ฟิลด์) {// และทำด้วยการเชื่อมต่อ connection.release (); // จัดการข้อผิดพลาดหลังรีลีส if (error) throw error; npmjs.com/package/mysql#pooling-connections
hpavc
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.