安裝
$ npm install mysql
簡介
這個一個mysql的nodejs版本的驅動,是用JavaScript來編寫的。不需要編譯
這兒有個例子來示范如何使用:
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();
從上面的例子,你可以學到:
1.connection每一個方法的調用都是被排隊的,而且被順序執行的
2.用 connection 的end 方法來關閉一個connection。 此方法會在結束前確保那些遺留的query被執行。之后才會發送一個quit packet 給mysql server 端
建立連接
推薦的建立連接的方法如下:
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方法隱式的打開
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(); } });
