[Node.js與數據庫]node-mysql 模塊介紹


轉載至:https://itbilu.com/nodejs/npm/NyPG8LhlW.html#multiple-statement-query

node-mysql是一個實現了MySQL協議的Node.js JavaScript客戶端,通過這個模塊可以與MySQL數據庫建立連接、執行查詢等操作,以下是根據官方文檔整理的一些模塊相關介紹。

 

  1. 連接
  2. 連接池
  3. 連接池集群(PoolCluster
  4. 執行SQL語句
  5. 其它

1. 連接

1.1 建立連接

安裝node-mysql模塊后,就可以使用這個模塊連接MySQL數據庫。建立連接可以使用createConnection()方法創建連接對象,再使用connect()建立連接:

var mysql      = require('mysql');
var connection = mysql.createConnection({
  host     : 'itbilu.com',
  user     : 'example',
  password : 'secret'
});

connection.connect(function(err) {
  if (err) {
    console.error('連接錯誤: ' + err.stack);
    return;
  }

  console.log('連接ID ' + connection.threadId);
});

 

建立連接也可以隱式的由查詢自動創建:

var connection = mysql.createConnection(...);

connection.query('SELECT 1', function(err, rows) {
  // 如果沒有錯誤,則會連接成功!
});

 

 

1.2 連接選項

在創建連接時,需要傳入一些選項。可傳入的選項參數如下:

  • host-連接主機名(默認為localhost
  • port-連接端口號(默認為3306
  • localAddress-使用TCP連接時的IP地址
  • socketPath-unix域套接字路徑,使用這個選項會忽略hostport選項
  • user-MySql連接用戶名
  • password-MySql連接密碼
  • database-要連接的數據庫
  • charset-連接使用的字符編碼(默認UTF8_GENERAL_CI
  • timezone-連接使用的時區(默認local
  • connectTimeout-連接超時時間(默認10000毫秒)
  • stringifyObjects-使用Stringify代替轉換值
  • insecureAuth-允許連接到MySQL實例(舊方法)
  • typeCast-列值是否轉換為本地JavaScript類型(默認true
  • queryFormat-自定義查詢格式函數
  • supportBigNumbers-處理大數字(BIGINT和DECIMAL)時需要啟動此項(默認false
  • bigNumberStrings-使用supportbignumbersbignumberstrings時總是返回JavaScript字符串對象(默認false
  • dateStrings-強制日期格式(TIMESTAMP, DATETIME, DATE)使用JavaScript日期對象(默認false
  • debug-打印協議詳細到stdout(默認false
  • trace-生成堆棧錯誤跟蹤信息(默認true
  • multipleStatements-允許每個查詢使用多個查詢語句(默認false
  • flags-使用默認值以外的連接標記列表
  • ssl-使用SSL連接對象

連接參數也可以一個查詢字符串的形式:

var connection = mysql.createConnection('mysql://user:pass@host/db?debug=true&charset=BIG5_CHINESE_CI&timezone=-0700');

SSL連接

SSL連接選項可以是一個對象或字符串。當是一個對象時,其選項和tls.createSecureContext()方法選項一樣,當是字符串時將返回一個預定義的SSL配置。

var connection = mysql.createConnection({
  host : 'localhost',
  ssl  : {
    ca : fs.readFileSync(__dirname + '/mysql-ca.crt')
  }
});

 1.3 關閉連接

關閉數據庫連接可以使用兩種方法。

通過end()方法,在執行結束后關閉連接:

connection.end(function(err) {
  // The connection is terminated now
});

 

別一種方式是使用destroy()方法,這個方法會立即關閉連接套接字,而不管執行是否完畢,且這個方法不end()方法沒有回調函數:

connection.destroy();

 

 

1.4 切換用戶和改變連接

MySQL可以在當前不關閉套接字的情況下切換用戶和改變連接,通過changeUser()方法能夠實現這一功能:

connection.changeUser({user : 'john'}, function(err) {
  if (err) throw err;
});

 

調用這一方法時可以傳入一個包含以下可選值的對象:

  • user-新用戶的用戶名(默認為之前用戶)
  • password-新用戶的密碼(默認為之前用戶密碼)
  • charset-新連接的編碼(默認為之前連接的字符編碼)
  • database-新連接的數據庫(默認為之前數據庫)

 

2. 連接池

數據庫連接是一種有限的,能夠顯著影響到整個應用程序的伸縮性和健壯性的資源,在多用戶的網頁應用程序中體現得尤為突出。

數據庫連接池正是針對這個問題提出來的,它會負責分配、管理和釋放數據庫連接,允許應用程序重復使用一個現有的數據庫連接,而不是重新建立一個連接,釋放空閑時間超過最大允許空閑時間的數據庫連接以避免因為連接未釋放而引起的數據庫連接遺漏。

數據庫連接池能明顯提高對數據庫操作的性能,node-mysql同樣支持建立連接池連接。

2.1 連接池連接

通過createPool()方法可以使用連接池連接:

var mysql = require('mysql');
var pool  = mysql.createPool({
  connectionLimit : 10,
  host            : 'itbilu.com',
  user            : 'example',
  password        : 'secret'
});

pool.query('SELECT 1 + 1 AS solution', function(err, rows, fields) {
  if (err) throw err;

  console.log('The solution is: ', rows[0].solution);
});

 

通過getConnection()方法連接可以共享一個連接,或管理多個連接:

var mysql = require('mysql');
var pool  = mysql.createPool({
  host     : 'example.org',
  user     : 'bob',
  password : 'secret'
});

pool.getConnection(function(err, connection) {
  // connected! (unless `err` is set)
});

 

關閉連接池連接

連接使用完后通過調用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(err, rows) {
    // And done with the connection.
    connection.release();

    // Don't use the connection here, it has been returned to the pool.
  });
});
 

 

銷毀連接池連接

connection.release()方法並不會將連接從連接池中移除,如果需要關閉連接且從連接池中移除,可以使用connection.destroy()

 

2.2 連接池選項

創建連接池時,我們同樣傳入了一個參數對象。所有在createConnection()方法中使用的參數,在創建連接池時同樣可以使用。創建連接池還以下幾個獨有的選項:

  • acquireTimeout:獲取連接的毫秒(默認:10000
  • waitForConnections:沒有連接或達到最大連接時連接的形為。為true時,連接池會將連接排隊以等待可用連接。為false將立即拋出錯誤(默認:true
  • connectionLimit:單次可創建最大連接數(默認:10
  • queueLimit:連接池的最大請求數,從getConnection方法前依次排隊。設置為0將沒有限制(默認:0

 

2.3 連接池事件

createPool()方法會返回一個連接池實例對象,這個對象中有一些事件。

'connection'

連接池中產生新連接時會發送'connection'事件,如果要在連接可用前設置一個session變量,需要監聽這個事件。

pool.on('connection', function (connection) {
  connection.query('SET SESSION auto_increment_increment=1')
});

 

'enqueue'

當回調已經排隊等待可用的連接時,連接池會發送一個'enqueue'事件。

pool.on('enqueue', function () {
  console.log('Waiting for available connection slot');
});

 

2.4 連接池關閉

當使用完連接池,要關閉所有連接,Node.js的事件循環在MySQL服務關閉前全然會有效。我們可以使用end()方法關閉連接池中的所有連接:

pool.end(function (err) {
  // all connections in the pool have ended
});

 

 

3. 連接池集群(PoolCluster

數據庫集群(Cluster)是利兩台或者多台數據庫服務器,構成一個虛擬單一數據庫邏輯映像,並像單數據庫系統那樣,向客戶端提供透明的數據服務。MySQL同樣支持建立數據庫集群,利用node-mysql模塊可以建立一個面向MySQL集群(MySQL Cluster)的連接。

3.1 連接池集群連接

PoolCluster使我們可以建立一個面向多台主機的連接,它由createPoolCluster()方法創建返回:

// 創建
var poolCluster = mysql.createPoolCluster();

// 添加配置
poolCluster.add(config); // anonymous group
poolCluster.add('MASTER', masterConfig);
poolCluster.add('SLAVE1', slave1Config);
poolCluster.add('SLAVE2', slave2Config);

// 移除配置
poolCluster.remove('SLAVE2'); // By nodeId
poolCluster.remove('SLAVE*'); // By target group : SLAVE1-2

// 目標群組 : 所有(anonymous, MASTER, SLAVE1-2), Selector : round-robin(default)
poolCluster.getConnection(function (err, connection) {});

// 目標群組 : MASTER, Selector : round-robin
poolCluster.getConnection('MASTER', function (err, connection) {});

// Target Group : SLAVE1-2, Selector : order
// If can't connect to SLAVE1, return SLAVE2. (remove SLAVE1 in the cluster)
poolCluster.on('remove', function (nodeId) {
  console.log('REMOVED NODE : ' + nodeId); // nodeId = SLAVE1 
});

poolCluster.getConnection('SLAVE*', 'ORDER', function (err, connection) {});

// of namespace : of(pattern, selector)
poolCluster.of('*').getConnection(function (err, connection) {});

var pool = poolCluster.of('SLAVE*', 'RANDOM');
pool.getConnection(function (err, connection) {});
pool.getConnection(function (err, connection) {});

// close all connections
poolCluster.end(function (err) {
  // all connections in the pool cluster have ended
});
 

 

3.2 連接池集群選項

創建集群連接時,可以傳入個含有以下可選值的參數對象:

  • canRetry:當為true時,PoolCluster會在連接失敗時嘗試重連(默認:true
  • removeNodeErrorCount:連接失敗時Node的errorCount計數會增加。當累積到這個值時移除PoolCluster這個節點(默認:5
  • restoreNodeTimeout:連接失敗后重試連接的毫移數(默認:0
  • defaultSelector:默認的選擇器(selector)(默認:RR
    • RR-依次選擇
    • RANDOM-隨機選擇
    • ORDER-選擇第一個可用節點

 

4. 執行SQL語句

對數據庫的操作都是通過SQL語句實現的,通過SQL語句可以實現創建數據庫、創建表、及對表中數據庫的增/刪/改/查等操作。

4.1 執行查詢

node-mysql中,通過ConnectionPool實例的query()執行SQL語句,所執行的SQL語句可以是一個SELECT查詢或是其它數據庫操作。query()方法有以下三種形式:

.query(sqlString, callback)

  • sqlString-要執行的SQL語句
  • callback-回調函數,其形式為function (error, results, fields) {}
connection.query('SELECT * FROM `books` WHERE `author` = "David"', function (error, results, fields) {
  // error 是一個錯誤對象,在查詢發生錯誤時存在
  // results 為查詢結果
  // fields 包含查詢結果的字段信息
});

 

.query(sqlString, values, callback)

  • sqlString-要執行的SQL語句
  • values-{Array},要應用到查詢占位符的值
  • callback-回調函數,其形式為function (error, results, fields) {}
connection.query('SELECT * FROM `books` WHERE `author` = ?', ['David'], function (error, results, fields) {
  // error 是一個錯誤對象,在查詢發生錯誤時存在
  // results 為查詢結果
  // fields 包含查詢結果的字段信息
});
 

 

.query(sqlString, values, callback)

  • options-{Object},查詢選項參數
  • callback-回調函數,其形式為function (error, results, fields) {}
connection.query({
  sql: 'SELECT * FROM `books` WHERE `author` = ?',
  timeout: 40000, // 40s
  values: ['David']
}, function (error, results, fields) {
  // error 是一個錯誤對象,在查詢發生錯誤時存在
  // results 為查詢結果
  // fields 包含查詢結果的字段信息
});

 

當使用參數占位符時,第二種和第三種形式也可以結合使用。如,可以將上例中的values值獨立為一個參數傳入:

connection.query({
  sql: 'SELECT * FROM `books` WHERE `author` = ?',
  timeout: 40000 // 40s
}, ['David'], function (error, results, fields) {
  // error 是一個錯誤對象,在查詢發生錯誤時存在
  // results 為查詢結果
  // fields 包含查詢結果的字段信息
});

 

 

4.2 查詢編碼與安全

為了防止SQL注入,可以傳入參數進行編碼。參數編碼方法有:mysql.escape()/connection.escape()/pool.escape(),這三個方法可以在你需要的時候調用:

var userId = 'some user provided value';
var sql    = 'SELECT * FROM users WHERE id = ' + connection.escape(userId);
connection.query(sql, function(err, results) {
  // ...
});

 

同樣的,也可以使用?做為查詢參數占位符,這與查詢值編碼效果是一樣的:

connection.query('SELECT * FROM users WHERE id = ?', [userId], function(err, results) {
  // ...
});

 

在使用查詢參數占位符時,在其內部自動調用connection.escape()方法對傳入參數進行編碼。

escape()方法編碼規則如下:

  • Numbers不進行轉換
  • Booleans轉換為true/false
  • Date對象轉換為'YYYY-mm-dd HH:ii:ss'字符串
  • Buffers轉換為hex字符串,如X'0fa5'
  • Strings進行安全轉義
  • Arrays轉換為列表,如['a', 'b']會轉換為'a', 'b'
  • 多維數組轉換為組列表,如[['a', 'b'], ['c', 'd']]會轉換為'a', 'b'), ('c', 'd')
  • Objects會轉換為key=value鍵值對的形式
  • undefined/null會轉換為NULL

使用查詢查詢占位符時時,其自動轉換如下:

var post  = {id: 1, title: 'Hello MySQL'};
var query = connection.query('INSERT INTO posts SET ?', post, function(err, result) {
  // Neat!
});
console.log(query.sql); // INSERT INTO posts SET `id` = 1, `title` = 'Hello MySQL'

 

如果你要自己進行編碼,可以像下面這樣的使用編碼函數:

var query = "SELECT * FROM posts WHERE title=" + mysql.escape("Hello MySQL");

console.log(query); // SELECT * FROM posts WHERE title='Hello MySQL'
 

 

4.3 編碼查詢標識符

如果你不信任用戶傳入的SQL標識符(數據庫、表、字符名),你可以使用mysql.escapeId(identifier)/connection.escapeId(identifier)/pool.escapeId(identifier)三個方法進行編碼:

var sorter = 'date';
var sql    = 'SELECT * FROM posts ORDER BY ' + connection.escapeId(sorter);
connection.query(sql, function(err, results) {
  // ...
});

 

這幾個方法也可以指定標識符:

var sorter = 'date';
var sql    = 'SELECT * FROM posts ORDER BY ' + connection.escapeId('posts.' + sorter);
connection.query(sql, function(err, results) {
  // ...
});

 

標識符也可以使用??標識符占位符,你可以像下面這樣使用:

var userId = 1;
var columns = ['username', 'email'];
var query = connection.query('SELECT ?? FROM ?? WHERE id = ?', [columns, 'users', userId], function(err, results) {
  // ...
});

console.log(query.sql); // SELECT `username`, `email` FROM `users` WHERE id = 1
 

 

4.4 流式查詢

.query()方法會返回一個query實例對象,它會發送一些特定的事件,這在進行大量數據查詢時非常有用:

var query = connection.query('SELECT * FROM posts');
query
  .on('error', function(err) {
    // 錯誤處理,在這個事件之后會發送一個'end'事件
  })
  .on('fields', function(fields) {
    // 查詢行字段包信息
  })
  .on('result', function(row) {
    // 暫停你正在使用進程的I/O操作
    connection.pause();

    processRow(row, function() {
      connection.resume();
    });
  })
  .on('end', function() {
    // 所有行查詢完成或發生錯誤后觸發
});

 

查詢流轉接

query對象提供了一個.stream([option])方法,它會將查詢事件包裝成一個可讀流,和對普通流的操作一樣,你可以對它進行暫停/恢復等操作。同樣也可將這個流通過pipe()方法轉接到另一個流中:

connection.query('SELECT * FROM posts')
  .stream({highWaterMark: 5})
  .pipe(...);

 

 

4.5 多語句查詢

出於安全考慮node-mysql默認禁止多語句查詢(可以防止SQL注入),啟用多語句查詢可以將multipleStatements選項設置為true

var connection = mysql.createConnection({multipleStatements: true});

啟用后可以在一個query查詢中執行多條語句:

connection.query('SELECT 1; SELECT 2', function(err, results) {
  if (err) throw err;

  // `results`是一個包含多個語句查詢結果的數組
  console.log(results[0]); // [{1: 1}]
  console.log(results[1]); // [{2: 2}]
});

 

多語句查詢同樣適用於流式查詢:

var query = connection.query('SELECT 1; SELECT 2');

query
  .on('fields', function(fields, index) {
    // the fields for the result rows that follow
  })
  .on('result', function(row, index) {
    // index refers to the statement this result belongs to (starts at 0)
  });

 

 

4.6 存儲過程

存儲過程可以在node-mysql的查詢中調用,和在其它驅動中調用並沒有區別。如果在存儲過程的執行結果中有多個數據集,其暴露方式和多語句查詢的使用一樣。

 

4.7 事務

事務的支持可以簡單的在connection級別上實現:

connection.beginTransaction(function(err) {
  if (err) { throw err; }
  connection.query('INSERT INTO posts SET title=?', title, function(err, result) {
    if (err) {
      return connection.rollback(function() {
        throw err;
      });
    }

    var log = 'Post ' + result.insertId + ' added';

    connection.query('INSERT INTO log SET data=?', log, function(err, result) {
      if (err) {
        return connection.rollback(function() {
          throw err;
        });
      }  
      connection.commit(function(err) {
        if (err) {
          return connection.rollback(function() {
            throw err;
          });
        }
        console.log('success!');
      });
    });
  });
});

 

在這個示例中,beginTransactioncommitrollback方法,其本質上都是執行了MySQL的START TRANSACTIONCOMMITROLLBACK命令。

 

5. 其它

5.1 Ping方法

檢測服務器是否可連接,可以使用connection.ping方法。當連接服務器沒有響應時,會將錯誤傳遞到回調函數的error參數中:

connection.ping(function (err) {
  if (err) throw err;
  console.log('Server responded to ping');
})

 

 

5.2 超時

每個操作都應該有一個可選的超時(timeout)選項,在操作超過指定時間后會發送超時錯誤。超時並不是MySQL協議的組成部分,而是客戶端的一個處理。

// 60秒后斷開查詢
connection.query({sql: 'SELECT COUNT(*) AS count FROM big_table', timeout: 60000}, function (err, rows) {
  if (err && err.code === 'PROTOCOL_SEQUENCE_TIMEOUT') {
    throw new Error('表 count 操作超時!');
  }

  if (err) {
    throw err;
  }

  console.log(rows[0].count + ' rows');
});

 

 

5.3 錯誤處理

這個模塊的錯誤處理機制與Node.js錯誤處理類似,其大多數錯誤對是JavaScript Error對象實例。在這個實例中有以下兩個額外屬性:

  • err.code-錯誤碼,可能是一個MySQL 服務端錯誤或Node.js錯誤或是一個內部錯誤
  • err.fatal-錯誤是否致命

err.fataltrue時,錯誤被傳遞致所有回調中。

var connection = require('mysql').createConnection({
  port: 84943, // WRONG PORT
});

connection.connect(function(err) {
  console.log(err.code); // 'ECONNREFUSED'
  console.log(err.fatal); // true
});

connection.query('SELECT 1', function(err) {
  console.log(err.code); // 'ECONNREFUSED'
  console.log(err.fatal); // true
});

 

一般的錯誤同樣會被傳遞,只會被傳遞至第一次回調中,再次查詢時依然可以正常進行:

connection.query('USE name_of_db_that_does_not_exist', function(err, rows) {
  console.log(err.code); // 'ER_BAD_DB_ERROR'
});

connection.query('SELECT 1', function(err, rows) {
  console.log(err); // null
  console.log(rows.length); // 1
});

 

如果發生一個致命錯誤而沒有等待回調,或一個正常的錯誤同樣沒有回調處理。錯誤會發出的一個到連接對象的'error'事件:

connection.on('error', function(err) {
  console.log(err.code); // 'ER_BAD_DB_ERROR'
});

connection.query('USE name_of_db_that_does_not_exist');

 

 

5.4 異常安全

這個模塊是異常安全的,這意味着你可以使用Node.js的'uncaughtException'事件或domain模塊進行異常捕獲。

 

5.5 類型轉換

node-mysql模塊會自動將MySQL數據類型轉換為JavaScript數據類型,其對應規則如下:

Number

  • TINYINT
  • SMALLINT
  • INT
  • MEDIUMINT
  • YEAR
  • FLOAT
  • DOUBLE

Date

  • TIMESTAMP
  • DATE
  • DATETIME

Buffer

  • TINYBLOB
  • MEDIUMBLOB
  • LONGBLOB
  • BLOB
  • BINARY
  • VARBINARY
  • BIT (last byte will be filled with 0 bits as necessary)

String

  • CHAR
  • VARCHAR
  • TINYTEXT
  • MEDIUMTEXT
  • LONGTEXT
  • TEXT
  • ENUM
  • SET
  • DECIMAL (may exceed float precision)
  • BIGINT (may exceed float precision)
  • TIME (could be mapped to Date, but what date would be set?)
  • GEOMETRY (never used those, get in touch if you do)

 

5.6 連接標識

如果你想修改默認的連接標識,可以連接選項的flags參數中傳入。一些可用的標識如下:

默認標識

以下是建立新連接時默認傳入的標識:

  • CONNECT_WITH_DB - 能夠在連接中指定的數據庫
  • FOUND_ROWS - 發送查找到的行替代受影響的行 affectedRows
  • IGNORE_SIGPIPE - 舊版本,不推薦使用
  • IGNORE_SPACE - 讓分析器忽略(之前的空格
  • LOCAL_FILES - 可以使用LOAD DATA LOCAL.
  • LONG_FLAG
  • LONG_PASSWORD - 使用改進版的舊密碼認證
  • MULTI_RESULTS - 可以處理多個結果集COM_QUERY
  • ODBC - 舊版本,不推薦使用
  • PROTOCOL_41 - 使用4.1版本的協議
  • PS_MULTI_RESULTS - 可以處理多個結果集COM_STMT_EXECUTE
  • RESERVED - 4.1版本的舊標識
  • SECURE_CONNECTION - 支持4.1版本的認證
  • TRANSACTIONS - 請求事務狀態的標識

multipleStatements選項為true時會發送以下標識:

  • MULTI_STATEMENTS - 每個查詢可以發送多個語句
  • TRANSACTIONS - 請求事務狀態的標識

其它可以標識

下面其它的可用標識。雖然其並一定起作用,但依然可以指定:

  • COMPRESS
  • INTERACTIVE
  • NO_SCHEMA
  • PLUGIN_AUTH
  • REMEMBER_OPTIONS
  • SSL
  • SSL_VERIFY_SERVER_CERT


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM