nodejs 訪問mysql

安裝javascript

$ npm install mysql

簡介html

這個一個mysql的nodejs版本的驅動,是用JavaScript來編寫的。不須要編譯java

這兒有個例子來示範如何使用:node

var mysql      = require('mysql');
var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'me',
  password : 'secret',
  database : 'my_db'
});

connection.connect();

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

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

connection.end();

 從上面的例子,你能夠學到:mysql

 1.connection每個方法的調用都是被排隊的,並且被順序執行的git

   2.用 connection 的end 方法來關閉一個connection。 此方法會在結束前確保那些遺留的query被執行。以後纔會發送一個quit packet 給mysql server 端github

創建鏈接sql

推薦的創建鏈接的方法以下:數據庫

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

connection.connect(function(err) {
  if (err) {
    console.error('error connecting: ' + err.stack);
    return;
  }

  console.log('connected as id ' + connection.threadId);
});

 另外,connection的打開也能夠被一個query方法隱式的打開npm

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

connection.query('SELECT 1', function(err, rows) {
  // connected! (unless `err` is set)
});

任何類型的鏈接錯誤(握手或者網絡)都是致命的。絕大多數錯誤是Error 對象的實例,另外error 有2個典型的屬性

  1.err.code :Mysql server error (ER_ACCESS_DENIED_ERROR),獲取一個nodejs error ECONNREFUSED ,獲取一個網絡error PROTOCOL_CONNECTION_LOST

  2.err.fatal : boolean類型,這個值指示這個error是否終止一個connection鏈接。假如這個error不是一個mysql protocol的錯誤,這個值應該是 not be defined

 致命的error(fatal)是要被傳播到全部的回調函數。以下:

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

 正常狀況下的error 僅僅被委託到他屬於的回調函數。以下:

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

 最後,若是一個致命的錯誤,或者一個正常的錯誤,沒有任何回調函數來處理,那麼connection 對象的error 事件將被 emit。

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

connection.query('USE name_of_db_that_does_not_exist');

 注意:'error' events,在node是很特別的,假如一個error發生,並且沒有任何函數來處理他,那麼堆棧信息將會被打印,進程將被kill。

鏈接參數

當創建一個鏈接,你能夠下面的參數

  • host:主機的名字,(默認:localhost)
  • port:主機端口號(默認3306)
  • localAddress:主機的ip地址(TCP鏈接,可選)
  • socketPath:主機是unix 的tcp鏈接地址,若是設置了host 和port,這個值被忽略
  • user:mysql 受權的用戶
  • password:mysql受權的用戶密碼
  • database:數據庫名稱
  • multipleStatements:多個查詢,(select 1,select 2. )。 處於安全考慮,默認false
  • charset:鏈接的字符編碼,(默認UTF8_GENERAL_CI)
  • timeZone:時區用來存儲本地日期,(默認local)
  • connectionTimeOut:鏈接超時 毫秒,(默認10000)
  • stringifyObjects:是否把對象字符串化(默認false)
  • typeCast:決定是否一個字段的值應該被轉化成一個原生的JavaScript類型的值(默認true)
  • queryFormat:自定義的query函數 
connection.config.queryFormat = function (query, values) {
  if (!values) return query;
  return query.replace(/\:(\w+)/g, function (txt, key) {
    if (values.hasOwnProperty(key)) {
      return this.escape(values[key]);
    }
    return txt;
  }.bind(this));
};

connection.query("UPDATE posts SET title = :title", { title: "Hello MySQL" });
  • supportBigNumbers:當處理大的數據的時候應該開啓這個選項(默認false),好比在數據庫類型中的bitint 或者decimal
  • bigNumberStrings:同時啓用bigNumberStrings和supportBigNumbers 將強制大數據結構(Bigint 或者decimal)以JavaScript中的String Objects 返回。(默認值false)。若是supportBigNumbers禁止,此選項將被忽略。若是supportBigNumber開啓,此選項關閉,那麼若是數字在 -2^53, +2^53 區間,那麼返回Number Object 不然返回String Object。
  • dateStrings:強制數據庫中的(TIMESTAMP, DATETIME, DATE)轉化成字符串不然返回JavaScript Date類型(默認false)
  • debug:是否在控制檯打印協議的信息(默認 false)
  • trace:在錯誤發生的時候打印堆棧信息,(默認true)
  • multipleStatements:待補充(默認false)
  • flag
  • ssl

另外除了以對象的形式傳送這些信息,也能夠使用字符串形式,以下:

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

 

終止鏈接

終止鏈接有兩種方式,比較優雅的方式是調用end方法。

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

假如在end的時候發生了致命的錯誤,err對象會在回調函數中啓用,可是connection都會被終止。

另一種方式是destory 方法,這將當即終端socket鏈接,destory 也沒有任何的事件和回調函數。

 

鏈接池

一個一個的建立和管理鏈接比較費事,mysql模塊提供了鏈接池。

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

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

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

鏈接池比分享單個鏈接和管理多個鏈接更加的簡單

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

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

當用一個connection完成操做時,僅僅須要調用connection.release()方法。connection 將會回到鏈接池中,準備下次鏈接

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

假如你想關閉這個鏈接和從鏈接池中移除這個鏈接,請調用destroy方法,鏈接池將在下次調用的時候建立新的鏈接。

鏈接池建立鏈接是懶加載的,假如你配置了100個鏈接上限,而你僅僅只用到了5個,那麼只有5個鏈接會被建立。鏈接池每次從隊列的頂部拿鏈接,release 以後的鏈接放在底部。

鏈接池參數

與建立鏈接時的參數相同,不過有一些額外的:

  • acquireTimeout:默認10000毫秒。一個鏈接捕獲的超時時長,這跟connectionTimeout不一樣,由於得到一個池鏈接並不老是涉及到鏈接.
  • waitForConnections:默認爲true。 假如true。在鏈接池沒有鏈接可用或者鏈接已經達到上限的時候,鏈接池將當即返回並攜帶error參數。 假如false,鏈接池將排隊等待鏈接可用。
  • connectionLimit:默認10個。
  • queueLimit:鏈接請求的最大上線數,若是超過這個數,將返回error。若是設置成0,則表示無限制,默認0.

鏈接池事件

 

  1.connetion:鏈接池將emit 一個connection event,當一個新的鏈接被建立。

    

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

      2.enqueue:鏈接池將emit 一個enqueue 事件,當一個connection 入棧

 

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

 

關閉鏈接池中全部的鏈接

當鏈接池結束使用,或者shutdown server 時候

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

這個回調函數,將在全部的query 執行以後被調用。 end 函數一旦被調用,pool.getConnetcion 將不在被執行

鏈接池集羣

todo....

切換用戶和改變當前的鏈接狀態

mysql 提供一個改變用戶和其餘鏈接屬性的命令,且不用shut down 當前的socket

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

參數:

  • user
  • password
  • charset
  • database

查詢

最基本的方式來建立一個查詢時調用.query方法(connection,pool等)

1.簡易的

connection.query('SELECT * FROM `books` WHERE `author` = "David"', function (error, results, fields) {
  // error will be an Error if one occurred during the query
  // results will contain the results of the query
  // fields will contain information about the returned results fields (if any)
});

2..query(sqlString, values, callback)

connection.query('SELECT * FROM `books` WHERE `author` = ?', ['David'], function (error, results, fields) {
  // error will be an Error if one occurred during the query
  // results will contain the results of the query
  // fields will contain information about the returned results fields (if any)
});

3..query(options, callback)

 

connection.query({
  sql: 'SELECT * FROM `books` WHERE `author` = ?',
  timeout: 40000, // 40s
  values: ['David']
}, function (error, results, fields) {
  // error will be an Error if one occurred during the query
  // results will contain the results of the query
  // fields will contain information about the returned results fields (if any)
});

 

編碼查詢的參數

爲了不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.query('UPDATE users SET foo = ?, bar = ?, baz = ? WHERE id = ?', ['a', 'b', 'c', userId], function(err, results) {
  // ...
});

不單單是 ? 替換。以下有各類狀況也會發生編碼:

  • 數字類型不受影響
  • Booleans 被 轉化成 true/false
  • 日期類型被轉化成YYYY-mm-dd HH:ii:ss
  • 字節類型,被轉化成16進制字符串,eg 0fa5
  • 數組被轉化成list,['a', 'b'] 轉成  'a', 'b'
  • 多重數組被轉成多重list,[['a', 'b'], ['c', 'd']] 轉成 ('a', 'b'), ('c', 'd')
  • 對象被轉化成 key=value 的形式,假如屬性值是fuction 就跳過,假如屬性值是object 就 調用 toString()方法
  • undefined/null 轉成 null
  • NAN/infinity Mysql不支持,若是插入會引起mysql 報錯

能夠有這樣優雅的實現

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'

 

編碼查詢標識

假如你不信任一個查詢標識(database,table,column).由於標識可能來自於用戶。你應該編碼這些標識,用mysql.escapeId(),connection.escapeId(),pool.escapeId().

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); // -> SELECT * FROM posts ORDER BY `posts`.`date`

假如想編碼 . 這個字符,把第二個參數設置成true。

var sorter = 'date.2';
var sql    = 'SELECT * FROM posts ORDER BY ' + connection.escapeId(sorter, true);

另外能夠用 ?? 字符來替換標識,

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

預查詢

var sql = "SELECT * FROM ?? WHERE ?? = ?";
var inserts = ['users', 'id', userId];
sql = mysql.format(sql, inserts);

 

自定義查詢格式化

connection.config.queryFormat = function (query, values) {
  if (!values) return query;
  return query.replace(/\:(\w+)/g, function (txt, key) {
    if (values.hasOwnProperty(key)) {
      return this.escape(values[key]);
    }
    return txt;
  }.bind(this));
};

connection.query("UPDATE posts SET title = :title", { title: "Hello MySQL" });

 

獲取剛插入行的ID

假如你正在插入一個表,且這個表有個自增加的ID,你能取到這個ID,以下:

connection.query('INSERT INTO posts SET ?', {title: 'test'}, function(err, result) {
  if (err) throw err;

  console.log(result.insertId);
});

獲取受影響的行數

insert, update or delete 

connection.query('DELETE FROM posts WHERE title = "wrong"', function (err, result) {
  if (err) throw err;

  console.log('deleted ' + result.affectedRows + ' rows');
})

獲取改變的行數

update語句,他不統計那些沒有改變值的行

connection.query('UPDATE posts SET ...', function (err, result) {
  if (err) throw err;

  console.log('changed ' + result.changedRows + ' rows');
})

流式查詢

大數據量時,要分包處理

var query = connection.query('SELECT * FROM posts');
query
  .on('error', function(err) {
    // Handle error, an 'end' event will be emitted after this as well
  })
  .on('fields', function(fields) {
    // the field packets for the rows to follow
  })
  .on('result', function(row) {
    // Pausing the connnection is useful if your processing involves I/O
    connection.pause();

    processRow(row, function() {
      connection.resume();
    });
  })
  .on('end', function() {
    // all rows have been received
  });

 注意:

  • pause() 方法是關閉流的閥門。
  • 不要爲這種流式的查詢提供回調函數
  • 不要pause 時間過長,不然將遇到error,(The server close the connection)。這個時間有mysql 服務的 net_write_timeout setting 決定

多條數據查詢

默認是關閉的,若是要開啓這個功能,須要在connection 選項中開啓 multipleStatements: true

一旦開啓,能夠這麼查詢:

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

  // `results` is an array with one element for every statement in the query:
  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)
  });

假如報錯了,err.index 屬性將告訴你哪一個sql語句出錯了。mysql 將不會執行下面的語句。

流式的多語句查詢是實驗性的。

 

Join 查詢

當遇到多表鏈接的join查詢,針對column名相同的狀況這麼處理

var options = {sql: '...', nestTables: true};
connection.query(options, function(err, results) {
  /* results will be an array like this now:
  [{
    table1: {
      fieldA: '...',
      fieldB: '...',
    },
    table2: {
      fieldA: '...',
      fieldB: '...',
    },
  }, ...]
  */
});
var options = {sql: '...', nestTables: '_'}; connection.query(options, function(err, results) { /* results will be an array like this now:  [{  table1_fieldA: '...',  table1_fieldB: '...',  table2_fieldA: '...',  table2_fieldB: '...',  }, ...]  */ });

事務

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

ping

一個ping包經過connection 發送給服務器

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

 

mysql To 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

自定義類型轉化

connection.query({
  sql: '...',
  typeCast: function (field, next) {
    if (field.type == 'TINY' && field.length == 1) {
      return (field.string() == '1'); // 1 = true, 0 = false
    }
    return next();
  }
});
相關文章
相關標籤/搜索