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