安裝
$ 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(); } });