node.js之mysql npm包學習


做學校項目時需要用node.js去連接mysql數據庫,於是打算將npm包中mysql的用法全部翻譯下來,順便整理筆記,原文傳送門

這是一個mysql的node.js驅動程序。他是用JS編寫的,不需要編譯,並且100%獲得MIT許可。下面是一個如何使用它的例子

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 (error, results, fields) {
  if (error) throw error;
  console.log('The solution is: ', results[0].solution);
});
 
connection.end();

從這個例子中,你可以學到以下內容:

1、你在連接上調用的每個方法都是按順序排隊執行的。

2、調用end()方法關閉連接,它確保所有剩余的查詢在發送一個退出連接的包到mysql數據庫前執行完成。

 

建立連接

推薦用這個方法來建立連接:

 

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

 

但是,一個連接也可以通過調用query方法被隱式建立:

var mysql      = require('mysql');
var connection = mysql.createConnection(...);
 
connection.query('SELECT 1', function (error, results, fields) {
  if (error) throw error;
  // connected!
});

這取決於你喜歡怎樣處理你的errors,兩種方法都可能是合適的。任何類型的連接錯誤(握手或網絡)都被認為是致命錯誤,有關更多信息,請參閱錯誤處理部分。

連接選項(只列出常用的)

當你建立一個連接的時候你可以設置以下選項:

host:你連接數據庫的主機名,默認是localhost;

port:你連接的端口號,默認3306;

localAddress:用於TCP連接的源IP地址

user:mysql用戶名;

password:mysql用戶對應的密碼;

database:連接的數據庫名;

charset:用於該連接的字符編碼。默認是UTF8_GENERAL_CI

timezone:mysql服務器上設置的時區。它用於將服務器日期/時間值轉換為js日期對象,反之亦然。可以是"local","z",或者+HH:MM或-HH:MM格式的偏移量。(默認值是“本地”)

connectTimeout:在初始連接MySQL服務器時超時前的毫秒數。(默認:10000)

datestring:強制日期類型(TIMESTAMP, DATETIME, date)作為字符串返回,而不是膨脹成JavaScript日期對象。可以是true/false,也可以是作為字符串保存的類型名數組。(默認值:false)

debug:打印協議細節到stdout。可以為true/false或應該打印的數據包類型名稱數組。(默認值:false)

trace:在出錯時生成堆棧跟蹤,包括圖書館入口的調用位置(“長堆棧跟蹤”)。大多數調用的性能損失較小。(默認值是true)

multipleStatements:允許每個查詢使用多個mysql語句。注意,這可能會增加SQL注入攻擊的范圍。(默認值:false)

flag要使用的連接標志的列表(默認連接標志除外)。也可以將默認的黑名單。有關更多信息,請檢查連接標志。

 

 

 

除了傳遞選項對象,你還可以使用url字符串。例如:

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

這個查詢值首先嘗試被解析成json對象,如果失敗了則轉換為文本字符串。

 

連接標志

由於各種原因,你想要改變默認的連接標志,你可以使用連接選項flags。傳遞一個以逗號分隔的項目列表字符串,以添加到默認標志。如果你不想使用默認標志,在標志前加上一個負號。要添加不在默認列表中的標志,只需寫入標志名,或在其前面加一個加號(不區分大小寫)。

 

var connection = mysql.createConnection({
  // disable FOUND_ROWS flag, enable IGNORE_SPACE flag
  flags: '-FOUND_ROWS,IGNORE_SPACE'
});

 

可用的標志自行查看官網~(你好懶)

 

終止連接

一共有兩種方式終止連接。調用end()方法可以簡潔地終止一個連接。

 

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

 

這將確保所有前面已經入隊地查詢仍然在發送一個COM_QUIT包到mysql數據庫之前。如果在發送COM_QUIT包之前發生了一個致命的錯誤(如握手失敗或者網絡擁堵等),一個err參數將會提供給回調函數。無論如何,連接都會終止。

另一個可選的方法是調用destroy()方法。這會導致socket連接立刻終止。另外destory()方法保證不會產生其他事件和回調會被觸發。

connection.destroy();

不像end()方法,destroy()方法沒有回調參數。

連接池

與一個一個創建和管理連接不同,該模塊還使用mysql.createPool(config)提供了內置的連接池。

創建一個連接池並且直接使用它:

 

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 (error, results, fields) {
  if (error) throw error;
  console.log('The solution is: ', results[0].solution);
});

 

這是pool.getConnection() -> connection.query() -> connection.release()代碼流的快捷方式。使用pool.getConnection()可以為后續查詢共享連接狀態。這是因為對pool.query()的兩次調用可能會使用兩個不同的連接並並行運行。這是基本結構:

var mysql = require('mysql');
var pool  = mysql.createPool(...);
 
pool.getConnection(function(err, connection) {
  if (err) throw err; // not connected!
 
  // Use the connection
  connection.query('SELECT something FROM sometable', function (error, results, fields) {
    // When done with the connection, release it.
    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.destory()代替release()。

池將在下次需要連接時創建一個新連接。

連接是由池惰性創建的。如果您將連接池配置為允許最多100個連接,但只同時使用5個連接,那么只會建立5個連接。連接也是循環循環的,連接從池的頂部獲取,然后返回到池的底部。

當從池中檢索到先前的連接時,一個ping包被發送到服務器,以檢查連接是否仍然有效。

連接池選項

連接池可以一個連接都是相同的選項。當創建一個新連接時,選項直接作為連接構造函數的參數。另外連接池接受幾個額外的選項:

1、acquireTimeout

在連接獲取過程中發生超時之前的毫秒數。這與connectTimeout稍有不同,因為獲取池連接並不總是涉及建立連接。如果連接請求進入隊列,則請求在隊列中花費的時間不計入此超時。(默認:10000)

2、waitForConnection

確定當沒有連接可用且已達到限制時池的操作。如果為true,則池將對連接請求進行排隊,並在連接請求可用時調用它。如果為false,池將立即回調一個錯誤。(默認值是真實的)

3、connectionLimit:一次連接的最大數量(默認是10);

4、queueLimit:queueLimit:在從getConnection返回錯誤之前,連接池將排隊的最大連接請求數。如果設置為0,則對排隊的連接請求數沒有限制。(默認值:0);

連接池事件

acquire

當從池中獲取連接時,池將發出一個acquire事件。在連接上執行了所有的獲取活動之后,也就是將連接交給獲取代碼的回調之前,調用這個函數。

pool.on('acquire', function (connection) {
  console.log('Connection %d acquired', connection.threadId);
});

 

connection

 

當在池中建立新連接時,池將發出連接事件。如果在使用連接之前需要在連接上設置會話變量,則可以偵聽連接事件。

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

enqueue

 

當回調函數排隊等待可用連接時,池將發出排隊事件。

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

 

 

 

release

當一個連接被釋放回連接池時,連接池會觸發release事件。在所有釋放活動執行完后調用它,所以在該事件發生時,連接處於空閑狀態。

 

pool.on('release', function (connection) {
  console.log('Connection %d released', connection.threadId);
});

 

 

關掉連接池中的所有連接

當你使用完一個連接池后,你不得不關閉它的所有連接否則node.js的事件循環會保持活躍直到連接被mysql關閉服務。如果在腳本中使用池,或者試圖優雅地關閉服務器,通常會這樣做。要結束池中的所有連接,請使用池中的end方法:

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

end方法右一個可選的回調函數使你可以知道所有的連接關閉了;

一旦end方法被調用,pool.getConnection方法和其他方法就不會再執行。所以在調用end方法之前要等待池中的連接釋放。如果你用了pool.query的快捷方法,也等待它完成再調用end方法。

pool的end方法會調用連接池中所有的connection的end方法,這將使quit包在連接中排隊並設置一個標志來防止連接池建立新的連接。所有正在執行的命令都將完成,但是新的命令將不會執行。

 

連接池集群

連接池集群提供多個主機的連接:

 

// create
var poolCluster = mysql.createPoolCluster();
 
// add configurations (the config is a pool config object)
poolCluster.add(config); // add configuration with automatic name
poolCluster.add('MASTER', masterConfig); // add a named configuration
poolCluster.add('SLAVE1', slave1Config);
poolCluster.add('SLAVE2', slave2Config);
 
// remove configurations
poolCluster.remove('SLAVE2'); // By nodeId
poolCluster.remove('SLAVE*'); // By target group : SLAVE1-2
 
// Target Group : ALL(anonymous, MASTER, SLAVE1-2), Selector : round-robin(default)
poolCluster.getConnection(function (err, connection) {});
 
// Target Group : 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
});
// A pattern can be passed with *  as wildcard
poolCluster.getConnection('SLAVE*', 'ORDER', function (err, connection) {});
 
// The pattern can also be a regular expression
poolCluster.getConnection(/^SLAVE[12]$/, 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) {});
pool.query(function (error, results, fields) {});
 
// close all connections
poolCluster.end(function (err) {
  // all connections in the pool cluster have ended
});

 

連接池集群選項

canRetry:如果為true,當連接失敗時,連接池集群會嘗試重連。默認為真。

removeNodeErrorCount:如果連接失敗,節點的errorCount會增長。當errorCount大於removeNodeErrorCount,將從連接池集群中移走一個節點。

restoreNodeTimeout:如果連接失敗,指定再次嘗試連接的毫秒數。如果設置為0,則node將被刪除,並且永遠不會被重用。(默認值:0)

defaultSelector:默認的選擇器。(默認值:RR)

 

RR:交替選擇。(循環)

 

RANDOM:按隨機函數選擇節點。

 

ORDER:無條件地選擇第一個可用的節點。

var clusterConfig = {
  removeNodeErrorCount: 1, // Remove the node immediately when connection fails.
  defaultSelector: 'ORDER'
};
 
var poolCluster = mysql.createPoolCluster(clusterConfig);

 

切換用戶和修改連接狀態

mysql提供一個切換用戶的命令來允許你改變當前用戶和其他方面的連接,而不關閉底層套接字:

 

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

 

對於這個特征可獲得的選項有:

user:你要切換的用戶名,默認是原用戶名。

password:切換的用戶密碼,默認是原密碼。

charset:新的字符編碼,默認是原字符編碼;

database:新的數據庫,默認是原數據庫。

這個功能的一個副作用是,這個函數還會重置任何連接狀態(變量、事務等)。

在此操作過程中遇到的錯誤將被此模塊視為致命連接錯誤。

服務器連接失敗

你可能由於網絡原因沒能成功連上mysql服務器

 

服務器超時,服務器重新啟動,或者崩潰。所有這些事件都被認為是致命錯誤,並且會出現err。代碼=“PROTOCOL_CONNECTION_LOST”。有關更多信息,請參閱錯誤處理部分。

 

重新連接連接是通過建立一個新的連接來完成的。一旦終止,現有的連接對象就不能通過設計重新連接。

 

使用池,斷開連接的連接將從池中刪除,從而釋放空間,以便在下一個getConnection調用中創建新的連接。

 

使用PoolCluster,斷開的連接將算作相關節點的錯誤,並增加該節點的錯誤代碼。一旦給定節點上的錯誤超過removeNodeErrorCount,則將其從集群中刪除。發生這種情況時,如果模式不再有任何匹配的節點,PoolCluster可能會發出POOL_NONEONLINE錯誤。restoreNodeTimeout配置可以設置為在給定超時后恢復離線節點。

 

執行查詢

最基礎的執行查詢方式是調用一個對象上的query()方法(像connnection,pool或者poolNamespace)。

query()方法最簡便的形式是.query(sqlString,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)
});

第二種形式是.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)
});

第三種形式.query(選項,回調)是在對查詢使用各種高級選項時出現的,比如轉義查詢值、連接重疊列名、超時和類型轉換:

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

請注意,在將占位符值作為參數傳遞而不是在options對象中傳遞時,可以使用第二和第三種形式的組合。values參數將覆蓋option對象中的值:

connection.query({
    sql: 'SELECT * FROM `books` WHERE `author` = ?',
    timeout: 40000, // 40s
  },
  ['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)
  }
);

如果查詢只有一個替換字符(?),且該值不是null、未定義或數組,它可以直接作為第二個參數傳遞給.query:

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

 

轉義查詢值

 

注意這些轉義值的方法只在禁用NO_BACKSLASH_ESCAPES SQL模式(這是MySQL服務器的默認狀態)時有效。

 

為了避免SQL注入攻擊,在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 (error, results, fields) {
  if (error) throw error;
  // ...
});

或者,你可以使用?字符來作為值的占位符,你可以這樣轉義:

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

多個占位符按照傳遞的順序映射到值。例如,在下面的查詢中foo等於a, bar等於b, baz等於c, id將是userId:

connection.query('UPDATE users SET foo = ?, bar = ?, baz = ? WHERE id = ?', ['a', 'b', 'c', userId], function (error, results, fields) {
  if (error) throw error;
  // ...
});

這看起來與MySQL中的預准備語句類似,但它實際上只是在內部使用了相同的connection.escape()方法。

不同的值類型有不同的轉義方式,如下所示:

數字保持不變

布爾值被轉換為真/假

Date對象被轉換為'YYYY-mm-dd HH:ii:ss'字符串

緩沖區被轉換為十六進制字符串,例如X'0fa5'

字符串是安全轉義的

數組被轉換為列表,例如['a', 'b']被轉換為'a', 'b'

嵌套數組被轉換成分組列表(用於批量插入),例如[['a', 'b'], ['c', 'd']]變成('a', 'b'), ('c', 'd')

具有toSqlString方法的對象將被調用.toSqlString(),並將返回值用作原始SQL。

對象被轉換為對象上每個可枚舉屬性的key = 'val'對。如果屬性的值是一個函數,則跳過它;如果屬性的值是一個對象,則對其調用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 (error, results, fields) {
  if (error) throw error;
  // Neat!
});
console.log(query.sql); // INSERT INTO posts SET `id` = 1, `title` = 'Hello MySQL'

並且toSqlString方法允許你用函數做如下復雜的查詢:

var CURRENT_TIMESTAMP = { toSqlString: function() { return 'CURRENT_TIMESTAMP()'; } };
var sql = mysql.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
console.log(sql); // UPDATE posts SET modified = CURRENT_TIMESTAMP() WHERE id = 42

要使用toSqlString方法生成對象,可以使用mysql.raw()方法。這將創建一個對象,當使用在?占位符,用於將函數作為動態值使用:

注意提供給mysql.raw()的字符串在使用時將跳過所有轉義函數,所以在傳入未經驗證的輸入時要小心。

var CURRENT_TIMESTAMP = mysql.raw('CURRENT_TIMESTAMP()');
var sql = mysql.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
console.log(sql); // UPDATE posts SET modified = CURRENT_TIMESTAMP() WHERE id = 42

如果你需要靠自己轉義查詢,你可以直接使用escaping 函數:

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

 

轉義查詢標識符


如果你不相信一個由用戶提供的sql標識(數據庫名,表明,列名),你應該用像這樣使用mysql.escapeId(identifier)connection.escapeId(identifier) or pool.escapeId(identifier) :

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

 

它還支持添加限定標識符。兩部分都能轉義:

 

var sorter = 'date';
var sql    = 'SELECT * FROM posts ORDER BY ' + connection.escapeId('posts.' + sorter);
// -> SELECT * FROM posts ORDER BY `posts`.`date`

 

如果你不想將point作為限定標識符,你可以將第二個參數設置為true,以保持字符串作為文字標識符:

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

或者,你可以使用??作為標識的占位符,以便你可以這樣轉義:

var userId = 1;
var columns = ['username', 'email'];
var query = connection.query('SELECT ?? FROM ?? WHERE id = ?', [columns, 'users', userId], function (error, results, fields) {
  if (error) throw error;
  // ...
});
 
console.log(query.sql); // SELECT `username`, `email` FROM `users` WHERE id = 1

請注意,最后一個字符序列是實驗性的,語法可能會改變

當您將對象傳遞給.escape()或.query()時,. escapeid()用於避免對象鍵中的SQL注入。

 

准備查詢

 

你可以使用mysql.format()方法來准備一個具有多個插入點的查詢,適當的利用轉義來轉義標識和值,如下一個簡單例子:

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

在此之后,您將獲得一個有效的、轉義的查詢,然后可以安全地將該查詢發送到數據庫。如果您希望在實際將查詢發送到數據庫之前准備好查詢,那么這是非常有用的。由於mysql.format()是通過SqlString.format()公開的,所以您還可以(但不是必需的)傳入stringifyObject和timezone,這允許您提供將對象轉換為字符串的自定義方法,以及特定於位置/時區的日期。

自定義格式

如果希望使用另一種類型的查詢轉義格式,可以使用連接配置選項定義自定義格式函數。如果想使用內置的.escape()或任何其他連接函數,可以訪問連接對象。

下面是如何實現另一種格式的例子:

 

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:

 

connection.query('INSERT INTO posts SET ?', {title: 'test'}, function (error, results, fields) {
  if (error) throw error;
  console.log(results.insertId);
});

 

當處理大數字(超過JavaScript數字精度限制)時,您應該考慮啟用supportBigNumbers選項,使其能夠將插入id讀取為字符串,否則將拋出錯誤。

在從數據庫中獲取較大的數字時也需要此選項,否則由於精度限制,您將得到四舍五入到數百或數千的值。

 

獲得受影響行的數目

你可以獲得由於插入,更新或者刪除語句而受影響的行數:

 

connection.query('DELETE FROM posts WHERE title = "wrong"', function (error, results, fields) {
  if (error) throw error;
  console.log('deleted ' + results.affectedRows + ' rows');
})

 

 

 

 

獲得受已改變行的數目

你可以獲得由於更新語句而改變的行的數目。

這不同於受影響的行,因為已改變行的數目不包括值未發生變化的行的行數。

 

connection.query('UPDATE posts SET ...', function (error, results, fields) {
  if (error) throw error;
  console.log('changed ' + results.changedRows + ' rows');
})

 

 

獲得連接ID

您可以使用threadId屬性獲得給定連接的MySQL連接ID(“thread ID”)。

connection.connect(function(err) {
  if (err) throw err;
  console.log('connected as id ' + connection.threadId);
});

 

 

並行執行查詢

MySQL協議是順序的,這意味着您需要多個連接來並行執行查詢。您可以使用一個連接池來管理連接,一種簡單的方法是為每個傳入的http請求創建一個連接。

 

流媒體查詢行

有時,您可能希望選擇大量的行,並在接收到它們時處理它們。可以這樣做:

 

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()和resume()操作底層socket和解析器。你完全可以保證調用pause()后result事件就不會再觸發。

在使用流式數據時,你一定不能給query()方法提供回調函數。

非常重要的一點是,不要讓結果停留太長時間,否則可能會遇到這樣的錯誤:Connection lost:服務器關閉了連接。這個時間限制由MySQL服務器上的net_write_timeout設置決定。

此外,您可能有興趣知道,目前還不可能流化單個行列,它們將總是被完全緩沖。如果你有一個很好的用於在MySQL之間傳輸大字段的用例,我很樂意聽取你的想法和貢獻。

帶有流的管道結果

 

query對象提供了一個方便的方法. Stream ([options]),它將查詢事件包裝到一個可讀的Stream對象中。該流可以很容易地被輸送到下游,並基於下游擁塞和可選的highWaterMark提供自動暫停/恢復。流的objectMode參數被設置為true並且不能更改(如果你需要一個字節流,你將需要使用一個轉換流,例如objstream)。

 

例如,將查詢結果管道到另一個流(最大緩沖區為5個對象)很簡單:

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

 

多語句查詢

出於安全原因,多語句查詢時禁止的。(如果值未正確地轉義這會允許sql注入攻擊)。要使用此功能,您必須為您的連接啟用它:

 

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

 

一旦開啟,你可以像這樣執行多語句查詢:

connection.query('SELECT 1; SELECT 2', function (error, results, fields) { if (error) throw error; // `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)
  });

如果查詢中的某個語句導致錯誤,則產生的error對象包含一個err.index屬性,該屬性告訴您是哪條語句導致了錯誤。當出現錯誤時,MySQL也將停止執行任何剩余的語句。

請注意,流多語句查詢的接口是實驗性的,我期待對它的反饋。

 

存儲過程

您可以從查詢中調用存儲過程,就像使用任何其他mysql驅動程序一樣。如果存儲過程產生多個結果集,它們將以與多個語句查詢的結果相同的方式向您公開。

 

使用重疊列名進行連接

執行join時,很可能會得到具有重疊列名的結果集。

默認情況下,node-mysql會按照從MySQL接收列的順序覆蓋沖突的列名,導致接收到的一些值不可用。

然而,你也可以指定你的列像這樣嵌套在表名下面:

var options = {sql: '...', nestTables: true};
connection.query(options, function (error, results, fields) {
  if (error) throw error;
  /* results will be an array like this now:
  [{
    table1: {
      fieldA: '...',
      fieldB: '...',
    },
    table2: {
      fieldA: '...',
      fieldB: '...',
    },
  }, ...]
  */
});

或者使用字符串分隔符來合並結果:

var options = {sql: '...', nestTables: '_'};
connection.query(options, function (error, results, fields) {
  if (error) throw error;
  /* results will be an array like this now:
  [{
    table1_fieldA: '...',
    table1_fieldB: '...',
    table2_fieldA: '...',
    table2_fieldB: '...',
  }, ...]
  */
});

Transactions

連接提供簡單的事務支持:

 

connection.beginTransaction(function(err) {
  if (err) { throw err; }
  connection.query('INSERT INTO posts SET title=?', title, function (error, results, fields) {
    if (error) {
      return connection.rollback(function() {
        throw error;
      });
    }
 
    var log = 'Post ' + results.insertId + ' added';
 
    connection.query('INSERT INTO log SET data=?', log, function (error, results, fields) {
      if (error) {
        return connection.rollback(function() {
          throw error;
        });
      }
      connection.commit(function(err) {
        if (err) {
          return connection.rollback(function() {
            throw err;
          });
        }
        console.log('success!');
      });
    });
  });
});

 

請注意,beginTransaction(), commit()和rollback()只是簡單的函數,分別執行開始事務,提交和回滾命令。如MySQL文檔中所述,MySQL中的許多命令都可能導致隱式提交,理解這一點很重要。

 

Ping

可以通過connection.ping()方法發送一個ping包到該連接上。此方法將向服務器發送一個ping包,當服務器響應時,回調將觸發。如果發生錯誤,回調函數將觸發一個error參數。

 

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

 

 

超時

每個操作都有一個可選的不活動超時選項。這允許您為操作指定適當的超時。需要注意的是,這些超時不是MySQL協議的一部分,而是通過客戶端的超時操作。這意味着,當達到超時時,發生超時的連接將被銷毀,並且無法執行進一步的操作。

// Kill query after 60s
connection.query({sql: 'SELECT COUNT(*) AS count FROM big_table', timeout: 60000}, function (error, results, fields) {
  if (error && error.code === 'PROTOCOL_SEQUENCE_TIMEOUT') {
    throw new Error('too long to count table rows!');
  }
 
  if (error) {
    throw error;
  }
 
  console.log(results[0].count + ' rows');
});

 

錯誤處理

該模塊提供了一種一致的錯誤處理方法,為了編寫可靠的應用程序,您應該仔細檢查該方法。

該模塊創建的大多數錯誤都是JavaScript Error對象的實例。此外,它們通常帶有兩個額外的屬性:

err.code:字符串。包含mysql服務器的錯誤符號,如果錯誤是mysql服務器:例如ER_ACCESS_DENIED_ERROR;如果是node.js錯誤,則為node.js錯誤代碼,如ECONNREFUSED。或者一個內部錯誤代碼PROTOCOL_CONNECTION_LOST.

err.errno:數字。mysql服務器的錯誤號。只能被mysql服務器錯誤填充。

err.fatal:boolean。指示此錯誤是否是連接對象的終止。如果錯誤不是來自MySQL協議操作,則不會定義此屬性。

err.sql:字符串。包含失敗查詢的完整SQL。當使用更高級的接口(如生成查詢的ORM)時,這可能非常有用。

err.sqlState:字符串,包含5個字符的sqlState值。只能被MySQL服務器錯誤填充。

err.sqlMessage:字符串,包含提供錯誤的文本描述的消息字符串。只能被MySQL服務器錯誤填充。

致命錯誤會傳遞到所有掛起的回調。在下面的示例中,當試圖連接到一個無效端口時,將觸發一個致命錯誤。因此,error對象會被傳遞到兩個掛起的回調中:

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 (error, results, fields) { console.log(error.code); // 'ECONNREFUSED'
  console.log(error.fatal); // true
});

但是正常的錯誤只提交給他們所屬的錯誤,所以在以下例子種,只有第一個回調收到了錯誤,第二個正常執行:

connection.query('USE name_of_db_that_does_not_exist', function (error, results, fields) { console.log(error.code); // 'ER_BAD_DB_ERROR'
}); connection.query('SELECT 1', function (error, results, fields) { console.log(error); // null
  console.log(results.length); // 1
});

最后但並非最不重要的:如果發生了致命錯誤且沒有掛起的回調,或者發生了沒有回調的正常錯誤,則該錯誤將作為connection對象上的'error'事件發出。下面的例子演示了這一點:

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

注意:'error'事件在node中是特殊的。如果它們在沒有附加偵聽器的情況下發生,則會打印堆棧跟蹤並殺死進程。

tl;dr:這個模塊不希望您處理靜默故障。你應該總是為你的方法調用提供回調。如果你想忽略這個建議並抑制未處理的錯誤,你可以這樣做:

// I am Chuck Norris:
connection.on('error', function() {});

異常安全的

此模塊是異常安全的。這意味着你可以繼續使用它,即使你的一個回調函數拋出一個錯誤,你正在用'uncaughtException'或域捕捉。

類型規划

為了方便,這個驅動程序將默認地將mysql類型轉換為原生JavaScript類型。存在以下映射關系:

Number

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

Date

  • TIMESTAMP
  • DATE
  • DATETIME

Buffer

  • TINYBLOB
  • MEDIUMBLOB
  • LONGBLOB
  • BLOB
  • BINARY
  • VARBINARY
  • BIT (最后一個字節將根據需要填充0比特)                                                                    說明二進制字符集中的文本作為緩沖區返回,而不是字符串。

    String(二進制字符集中的文本作為緩沖區返回,而不是字符串。)

    • CHAR
    • VARCHAR
    • TINYTEXT
    • MEDIUMTEXT
    • LONGTEXT
    • TEXT
    • ENUM
    • SET
    • DECIMAL (可能超過浮點精度)
    • BIGINT (可能超過浮點精度)
    • TIME (可以映射到日期,但將設置成什么日期?)
    • GEOMETRY (從未使用過,如果使用過請聯系)

 

不建議禁用類型轉換(可能會在將來消失/更改),但目前你可以在任意一個連接上這樣做:

var connection = require('mysql').createConnection({typeCast: false});

或者查詢時:

var options = {sql: '...', typeCast: false}; var query = connection.query(options, function (error, results, fields) { if (error) throw error; // ...
});

自定義類型規划

您還可以自己傳遞一個函數和處理類型轉換。您將得到一些列信息,如數據庫、表和名稱,以及類型和長度。如果您只是想將自定義類型轉換應用到特定類型,您可以這樣做,然后回退到默認類型。

該函數提供了兩個參數field和next,並期望通過field對象調用解析器函數返回給定字段的值。

field參數是一個Field對象,包含關於需要解析的字段的數據。以下是Field對象的一些屬性:

db:來自數據庫字符串的字段

table:來自表字符串的字段

name:字段名字符串

type:所有大寫的字段類型的字符串

length:字段長度,有數據庫給出。

 

 

下一個參數是一個函數,當被調用時,它將返回給定字段的默認類型轉換。

 

當獲取字段數據時,下面的更有用的方法出現在字段對象上:

 

.string() -將字段解析為字符串。

 

. Buffer() -將字段解析為緩沖區。

 

.geometry() -將該字段解析為一個幾何值。

MySQL協議是基於文本的協議。這意味着在連接過程中,所有字段類型都表示為字符串,這就是為什么field對象上只有類似字符串的函數可用。根據類型信息(如INT),類型轉換應該將字符串字段轉換為不同的JavaScript類型(如數字)。

下面是一個將TINYINT(1)轉換為布爾值的例子:

connection = mysql.createConnection({
  typeCast: function (field, next) {
    if (field.type === 'TINY' && field.length === 1) {
      return (field.string() === '1'); // 1 = true, 0 = false
    } else {
      return next();
    }
  }
});

 

注意:您必須在自定義類型轉換回調中使用這三個字段函數中的一個來調用解析器。它們只能被調用一次。

 

 

調試和報告問題

 

如果你遇到了問題,打開連接的調試模式可能會有幫助:

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

這將在標准輸出上打印所有傳入和傳出的數據包。你也可以通過傳遞一個類型數組來限制對數據包類型的調試:

var connection = mysql.createConnection({debug: ['ComQueryPacket', 'RowDataPacket']});

將調試限制在查詢報文和數據報文上。

如果這沒有幫助,請隨意打開GitHub問題。一個好的GitHub問題會有:

重現問題所需的最少代碼量(如果可能的話)

盡可能多的調試輸出和關於環境(mysql版本、節點版本、os等)的信息。

 

finish。愉快的參與到基於node.js的mysql開發叭~

 


免責聲明!

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



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