Node-MySQL:如何在node.js里连接和使用mysql
安装
$ npm install mysql
有关之前的0.9.x版本的信息, 请访问 v0.9分支。
有时我还会要求你从Github安装最新版以检查bug是否已修复。在这种情况下,请输入:
$ npm install mysqljs/mysql
引言
这是node.js的mysql驱动。它是用JavaScript编写的,不需要编译,完全遵循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(err, rows, fields) { if (err) throw err; console.log('The solution is: ', rows[0].solution); }); connection.end();
从这个例子中,你可以了解到以下几点:
- 对于一个连接,你所调用的每个方法都是按顺序排队并依次执行的。
- 使用end()关闭连接,以确保给mysql服务器发送退出(quit)包以前执行所有剩余的查询。
推荐的建立链接的一个方法:
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); });
然后,我们可以通过建立一个连接来进行查询:
var mysql = require('mysql'); var connection = mysql.createConnection(...); connection.query('SELECT 1', function(err, rows) { // connected! (unless `err` is set) });
以上二种方法都是正确且合适的。至于如何取舍,就要看你怎么去处理所遇到的错误了。不管哪种类型的错误,那都是致命的,我们需要去看所提示的具体的错误信息。
连接参数
在建立新连接时,可以设置以下参数:
- host:连接的数据库地址。(默认:localhost)
- port:连接地址对应的端口。(默认:3306)
- localAddress: 源IP地址使用TCP连接。(可选)
- socketPath:当主机和端口参数被忽略的时候,可以填写一个Unix的Socket地址。
- user: mysql的连接用户名。
- password: 对应用户的密码。
- database: 所需要连接的数据库的名称。(可选)
- charset: 连接的编码形式。这就是mysql中的整理。(例如:utf8_general_ci)如果被指定,则作为默认的整理排序规则。(默认:utf8_general_ci)
- timezone:用来保存当前本地的时区。(默认:local)
- connectTimeout: 设置在连接的时候,超过多久以后未响应则返回失败。(默认:10000)
- stringifyObjects: stringify对象代替转换值。issue# 501。(默认:false)
- insecureAuth:使用旧(不安全)的连接方式去连接MySQL。(默认:false)
- typeCast: 确定列值是否需要转换为本地JavaScript类型。(默认:true)
- queryFormat:自定义查询的方式。地址:Custom format.
- supportBigNumbers: 如果你使用了BIGINT和DECIMAL格式的表列,那么需要开启这个参数来支持。(默认:false)只有当他们超过JavaScript所能表达的 最长的字节的时候,如果没有设置这个参数,则会将过长的数字作为字符串传递。否则,返回对象的长度。如果supportBigNumbers参数被忽略, 则这个参数也会被忽略。
- dateStrings:一些日期类型(TIMESTAMP, DATETIME, DATE)会以Strings的类型返回,然后转换成JavaScript的日期对象。(默认:false)
- debug:是否把连接情况打印到文件。(默认:false)
- trace: 生成错误的堆栈跟踪,包括库入口的调用位置(“长堆栈的轨迹”)。一般会造成轻微的性能损失。(默认:true)
终止连接
终止连接的方法有两种。调用end()方法可以正常地终止一个连接:
connection.end(function(err) { // 连接终止 });
这种方法将确保给MySQL服务器发送COM_QUIT包之前所有队列中的查询都会被执行。如果在发送COM_QUIT包之前发生了致命错误,那么会给回调函数传递一个err参数,但是不管怎样连接都会关闭。
另外一种终止连接的方法是调用destroy()方法。该方法会立即终止底层套接字(underlying socket)。另外,destroy()不会触发更多的事件和回调函数。
connection.destroy();
和end()方法不同,destroy()方法不使用回调参数。
连接池连接
直接使用连接池。
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.release()方法使连接返回到连接池,以便其他人可以再次使用。
var mysql = require('mysql'); var pool = mysql.createPool(...); pool.getConnection(function(err, connection) { // 使用连接 connection.query( 'SELECT something FROM sometable', function(err, rows) { // 使用连接执行查询 connection.release(); //连接不再使用,返回到连接池 }); });
如果你想关闭连接并从连接池中删除它,就要使用connection.destroy()方法。在下次需要时连接池会再创建一个新的连接。
连接池创建连接很慵懒。如果你配置的连接池最大支持100个连接,但同时只使用了5个连接,那么它只创建5个连接。连接还采用了循环轮转方式,即连接建立在连接池顶部,返回到连接池底部。
当从连接池中恢复之前的某个连接时,会给服务器发送一个ping包以检查连接是否正常。
连接池的配置参数
连接池接受所有与连接相同的配置参数。创建新连接时,配置参数只是简单的传给连接对象的构造器。除此配置外,连接池还支持一些额外的参数:
- acquireTimeout(获取超时时间): 获取连接时,触发连接超时之前的毫秒数。这与connectTimeout略有不同,因为从连接池获取连接并不总会创建连接 (默认值:10000)
- waitForConnections(连接等待时间): 当无连接可用或连接数达到上限的时候,判定连接池动作。如果为true,连接池会将请求加入队列,待可用之时再触发操作;如为false,连接池将立即返回错误 (默认值:true)
- connectionLimit(连接数限制): 所允许立即创建的最大连接数量 (默认值: 10)
- queueLimit(队列数量限制): 在调用getConnection返回错误之前,连接池所允许入队列的最大请求数量。如设置为0, 则不限制。 (默认值: 0)
连接池的事件
连接
连接池里面创建了一个新连接时,会触发一个连接事件。如需要在使用此连接之前设置会话变量,将要对此事件进行监听。
pool.on('connection', function (connection) { connection.query('SET SESSION auto_increment_increment=1') });
入列
队列中等待可用连接的回调函数被触发时,连接池将触发此事件。
pool.on('enqueue', function () { console.log('Waiting for available connection slot'); });
在连接池中关闭所有连接
如不再需要连接池时,你必须关闭所有连接。否则Node.js脚本的事件流一直处于活跃状态,最终会被MySQL服务器会关闭。脚本占用连接池,这是典型的情况!另外可以以优雅的方式的关闭服务器。关闭所有连接池中的连接,中使用end方法:
pool.end(function (err) { // all connections in the pool have ended });
end方法接受一个可选的回调函数,以通知(一次)所有已关闭的连接。优雅关闭连接,队列中所有的查询都能被执行,但关闭连接池的时间会不一样。
一旦pool.end()被调用,pool.getConnection及其它操作将不再被执行!
集群连接池
集群连接池提供多主机连接.(分组&重试&选择器)
// 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 }); poolCluster.getConnection('SLAVE*', 'ORDER', 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) {}); // close all connections poolCluster.end(function (err) { // all connections in the pool cluster have ended });
集群连接池选项
- canRetry: 如果为true,当连接失效时,集群连接池将尝试重新连接. (默认: true)
- removeNodeErrorCount: 如果连接失效,节点的错误次数增加.当错误次数大于removeNodeErrorCount时,该节点从集群链接池中移除 (默认: 5)
- restoreNodeTimeout: 如果连接失效,该参数用于指定尝试获取另一个连接时需要等待的时间,时间单位为毫秒.如果为0,这个节点将被移除并且不会被重复使用.(默认值: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 提供了一个changeUser 命令,该命令允许你在不关闭下列socket的情况下,改变当前用户和连接的其余部分.
connection.changeUser({user : 'john'}, function(err) { if (err) throw err; });
该特性的可用选项有:
- user: 新的用户名 (默认前一次使用的用户名).
- password: 新用户的密码(默认前一次使用的密码).
- charset: 新的字符集 (默认前一次使用的字符集).
- database: 新的数据库名称(默认前一次使用的数据库名).
有时有用的是这个功能的副作用,即会重置任何连接的状态(变量,事物等).
在集群连接池模式下执行changeUser操作如果遇到错误将被视为致命错误.
服务断开连接
由于网络问题、MySQL服务器重启或崩溃,连接超时等问题,你有可能会断开与MySQL服务器的连接.所有上述事件被认定为致命错误,并且将返回错误码’PROTOCOL_CONNECTION_LOST’.查看更多信息,请参照错误处理章节.
下一个连接将以建立一个新的连接的方式来获取.当前设计下,一旦终止,现有的一个连接对象将不能够被重连.
连接池状态下,端开的链接将会从连接池中移除并释放空间,下一次调用getConnection时将用释放的空间创建新连接。
执行查询
最基本的执行查询的方法是在一个对象中调用 .query()函数 (比如一个连接或应用池实例)。
最简单的 .query()形式是 .query(sqlString, callback),第一个参数是一条SQL字符串,第二个参数是回调:
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()形式是 .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()形式是 .query(options, callback),在查询时带有大量的高级可选项,比如 转义查询值(escaping query values),联结重叠列名(joins with overlapping column names),超时(timeouts), 和 类型转换(type casting)。
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) });
注意一种第二和第三形式的结合体也可使用,其中占位符以参数来传递,并且不能做为选项对象。值参数会覆盖选项对象中的值。
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) } );
查询值转义
为了防止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(err, results) { // ... });
另外,也可以使用 ? 作为查询字符串中的占位符,替代你想要转义的值。例如:
connection.query('SELECT * FROM users WHERE id = ?', [userId], function(err, results) { // ... });
使用多个占位符则传入的值会依次填入。例如,下方查询中foo将等于a、bar将等于b、baz将等于c,而id将会被赋值为userId的值:
connection.query('UPDATE users SET foo = ?, bar = ?, baz = ? WHERE id = ?', ['a', 'b', 'c', userId], function(err, results) { // ... });
这样看起来与MySQL中prepared statements相似,但它内部其实使用了之前的connection.escape() 方法。
注意 它与prepared statements的另一点不同之处是,即使是在注释和字符串中的 ? 也会被替换。
不同类型的值转义的方式是有区别的,其区别如下:
- 数字不会被转义
- 布尔值会被转移成 true / false
- Date 对象会被转义成形如 ‘YYYY-mm-dd HH:ii:ss’ 的字符串
- Buffer 会被转义成十六进制字符串,如: X’0fa5′
- 字符串会被安全地转义
- 数组会被转义成列表,例如: [‘a’, ‘b’] 会被转义成 ‘a’, ‘b’
- 嵌套数组会被转义成多个列表(在大规模插入时),如: [[‘a’, ‘b’], [‘c’, ‘d’]] 会被转义成 (‘a’, ‘b’), (‘c’, ‘d’)
- 对象的所有可遍历属性会被转义成键值对。如果属性的值是函数,则会被忽略;如果属性值是对象,则会使用其 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'
当你觉得有必要亲自对这些查询进行转义时,也可以直接使用转义方法:
var query = "SELECT * FROM posts WHERE title=" + mysql.escape("Hello MySQL"); console.log(query); // SELECT * FROM posts WHERE title='Hello MySQL'
查询标识符转义
如果用户提供了不可信的查询标识符(数据库名、表名、列名),你应该用 mysql.escapeId(identifier), connection.escapeId(identifier) 或 pool.escapeId(identifier) 方法对它进行转义,如:
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); connection.query(sql, function(err, results) { // ... });
另外,你还可以用 ?? 作为占位符来替代你想要转义的标识符,如:
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
请注意,最后这部分的顺序是实验性的,其句法可能在之后的版本中改变。
当你将对象传给 .escape() 或 .query()时,为了防止SQL注入,对象的键将被 .escapeId() 转义。
预查询
你可以使用 mysql.format 来创建一个多插入点的查询语句,对id和值可以使用适当的转义处理 。下面是一个简单的例子:
var sql = "SELECT * FROM ?? WHERE ?? = ?"; var inserts = ['users', 'id', userId]; sql = mysql.format(sql, inserts);
这样你就获得了一个有效并且安全的查询语句,然后可以把它发送给 数据库。如果你希望在发送给数据库之前就准备好查询语句,这种方法很有用。因为mysql.format是从sqlString.format派生而来 的,所以你还可以选择传递stringifyObject 和timezone对象(但不强制要求),并且允许您通过自定义的方式将对象转换成字符串,以及特定地区的时间和时区(location specific/timezone aware Date).
自定义格式
如果你想使用其他样式的转义格式,你可以在连接配置选项中自定义一个自定义的格式函数。如果你还想使用内置的escape()函数或其它的连接函数,可以访问connection对象。
下面是一个实现自定义格式的例子:
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(err, result) { if (err) throw err; console.log(result.insertId); });
当处理大数字的时候(超过javascript的Number类型的精度),你需要启用supportBigNumbers选项,以便把插入行的ID作为字符串类型读取出来,否者会抛出一个错误。
当从数据库中查询的数字是一个big number类型的时候,也需要把supportBigNumbers选项设置为true,否则查询出来的数据由于精度限制,会进行四舍五入。
取得受影响的行数
你可以从 insert,update 或者 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'); })
获取连接ID
你可以取得MySQL的连接ID(“线程ID”),这是一个给定的连接,使用的是线程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) { // 处理错误,这之后会触发 'end' 事件 }) .on('fields', function(fields) { // 字段信息 }) .on('result', function(row) { // 暂停连接。如果你的处理过程涉及到 I/O 操作,这会很有用。 connection.pause(); processRow(row, function() { connection.resume(); }); }) .on('end', function() { // 所有数据行都已经接收完毕 });
在上面的示例中,有几点需要注意:
- 通常会在接收到一定数量的数据行之后使用 pause() 来暂停连接。这个数量取决于数据行数量和大小。
- pause()/resume() 操作基于套接字和解析器。在调用 pause() 后可以保证不会继续触发 ‘result’ 事件。
- 一定不能给流式查询行的 query() 调用提供回调函数。
- 查询到数据行会触发 ‘result’,INSERT/UPDATE 查询成功的时候也会触发 ‘result’
- 非常重要一点,不要暂停太久,否则会遇到 错误:连接丢失:服务器已断开连接。这个时间限制是在 MySQL 服务器中由 net_write_timeout setting 设置的。
此外,你可能需要知道,目前不可能在流式处理过程中处理单独的行列,它们总是完全缓存的。如果你在 MySQL 方面有流式处理大量字段的经验,希望你能在这里分享。
通过 Streams2 管道输出
查 询对象提供了一个便利的方法 .stream([options]) 将查询事件封装在 Readable Streams2 对象中。这个流对象能通过管道简单的流向下游,并基于下游阻塞程度和 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(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) });
如果查询中的某一条语句产生错误,则 Error 对象会有一个 err.index 属性用于指出是第几条语句导致的错误。一旦有错误发生,MySQL 会停止执行剩下的语句。
注意,流式处理多语句查询的接口是试验性的,我期待着对它的反馈。
存储过程
你可以调用你的存储过程,这来自于你的查询,就像调用任何 mysql 驱动一样。如果存储过程产生多个结果集,作为多个语句查询的结果,它们会以同样的方式暴露在你面前。
join 重叠的列名
当执行 join 操作时,你可以取得类似的结果集来重叠列名。
默认的方式是,node-mysql 将会按顺序覆盖从 MySQL 接收到的重叠列名,这会导致一些接收到的值不可用。
另外,你也可以指定你想要的列,嵌套以下这样的表名:
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!'); }); }); }); });
请记住 beginTransaction(), commit() 和 rollback() 是简单方便的函数,这些函数分别执行 START TRANSACTION,COMMIT 和 ROLLBACK 命令。重要的是要理解在 MySQL 中的很多函数所引发的隐含提交,这在MySQL文档中是有相应的描述的。
Ping
连接可以使用connection.ping方法向服务器发送一个ping包,若服务器作出响应,将触发回调函数。如果有错误发生,err参数将会传递给回调函数。
connection.ping(function (err) { if (err) throw err; console.log('Server responded to ping'); })
超时
每个操作都可以使用一个可选的固定超时(timeout )选项。这样你就可以为操作指定合适的超时时间。要注意的是超时并非MySQL 协议的内容,也不会通过客户端执行,这一点很重要。也就是说,当达到超时时间时,对应的连接将被终止,也不会再执行其它的操作。
// 60秒后终止查询 connection.query({sql: 'SELECT COUNT(*) AS count FROM big_table', timeout: 60000}, function (err, rows) { if (err && err.code === 'PROTOCOL_SEQUENCE_TIMEOUT') { throw new Error('too long to count table rows!'); } if (err) { throw err; } console.log(rows[0].count + ' rows'); });
错误处理
这个模块自带了一种统一的错误处理方法,你必须仔细查看以便写出健壮的应用程序。
该模块创建的大多数错误都是JavaScript的Error对象实例。另外,它们通常还会附带两个额外的属性:
- err.code: MySQL服务器错误 (比如’ER_ACCESS_DENIED_ERROR’), Node.js错误(比如’ECONNREFUSED’)或者内部错误(比如’PROTOCOL_CONNECTION_LOST’).
- err.fatal: 布尔型, 指出这个错误是否是终端连接对象。 如果错误不是来自MySQL协议操作,那这个属性不被定义。
致命错误会挂起所有的回调。在下面的例子中,试图连接到一个无效端口时触发一个致命错误。因此错误对象传递到了两个回调中:
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 });
然而,正常的错误只返回给属于他们的回调函数。所以在下面的例子中,只有第一个回调会接收错误,第二个查询的会出错:
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 });
最后但并非不重要:如果发生了一个致命的错误,但没有一个对应的回调,或者发生了一个正常的错误但没有属于他的回调,那么错误会向连接对象发出’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() {});
异常(exception)的安全
这个模块是异常(exception)安全的。这意味着你可以持续使用它,它是你的一个回调函数,它会抛出一个错误,你可以使用’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)
注意 在二进制字符集合中的文本是作为一个 Buffer 返回的,相当于是一个字符串。
- CHAR
- VARCHAR
- TINYTEXT
- MEDIUMTEXT
- LONGTEXT
- TEXT
- ENUM
- SET
- DECIMAL (可能超出浮点数的精度)
- BIGINT (可能超出浮点数的精度)
- TIME (可以映射成日期型(Date), 但是日期是一个集合?)
- GEOMETRY (从来没使用过,如果你用过请联系我)
不推荐(将来可能消失/改变)关闭类型转换,但是你现在可以做这样的连接:
var connection = require('mysql').createConnection({typeCast: false});
或者是在查询级别这么做:
var options = {sql: '...', typeCast: false}; var query = connection.query(options, function(err, results) { });
你可以始终通过一个函数来处理类型转换。你可以给定一些列信息,就像数据库,表,类型和长度。如果你想申请自定义类型转换成指定的你可以处理的类型,那么回调函数是默认的。这里举个例子,将 TINYINT(1) 转换成 boolean:
connection.query({ sql: '...', typeCast: function (field, next) { if (field.type == 'TINY' && field.length == 1) { return (field.string() == '1'); // 1 = true, 0 = false } return next(); } });
警告: 在你的自定义类型的回调函数中,你必须调用这三个 field 函数来解析。他们只能被调用一次(查看 讨论 #539)
field.string() field.buffer() field.geometry() are aliases for parser.parseLengthCodedString() parser.parseLengthCodedBuffer() parser.parseGeometryValue()
你可以通过仔细查找,找到 field 函数:
RowDataPacket.prototype._typeCast
连接标志
无论何种原因,你要是想改变默认的连接标志,你可以使用连接选项标记。通过逗号分隔字符串,把条目添加到默认的标志中。如果你不想要默认的准备好的标志,你可以用一个负号。添加一个不在默认列表中的标记,仅需要写出标记名称,或者在标记前加一个+号(大小写敏感)。
请记住一些有用的标记,它们是没有被提供的标记(例如:Compression),它们还没有被指定。
例子
下面例子中的黑名单 FOUND_ROWS 标记 来自默认的连接标记。
var connection = mysql.createConnection("mysql://localhost/test?flags=-FOUND_ROWS");
默认标记
下面的标记被发送是通过一个默认的新连接:
- CONNECT_WITH_DB – 指定数据库在连接上的能力。
- FOUND_ROWS – 发送发现affectedRows行而受影响的行。
- IGNORE_SIGPIPE – 老的;无影响。
- IGNORE_SPACE – 让解析器忽略空格之前的(查询。
- LOCAL_FILES – 可以使用 LOAD DATA LOCAL.
- LONG_FLAG
- LONG_PASSWORD – 使用改进的老密码验证版本。
- MULTI_RESULTS – 为 COM_QUERY 可以处理多个结果。
- ODBC Old; 无影响。
- PROTOCOL_41 – 使用4.1协议。
- PS_MULTI_RESULTS – 为 COM_STMT_EXECUTE 可以处理多个结果。
- RESERVED – 4.1 协议的老标记。
- SECURE_CONNECTION – 支持本地4.1认证。
- TRANSACTIONS – 询问事务状态标记。
另外,如果选项 multipleStatements 设置成 true,下面这些将会被发送:
- MULTI_STATEMENTS – 每次查询或者准备声明,客户端都可能会发送多个子句。
其他有用的标记
有其他一些可用的标记。他们可能会也可能不会起作用,但是仍然可用来指定。
- COMPRESS
- INTERACTIVE
- NO_SCHEMA
- PLUGIN_AUTH
- REMEMBER_OPTIONS
- SSL
- SSL_VERIFY_SERVER_CERT
调试和报告问题
如果你遇到问题,你可以在连接时激活调试模式来辅助:
var connection = mysql.createConnection({debug: true});
这将打印所有传入和传出的数据包到 stdout 上。你也可以限制调试包的数组类型,并传递至调试:
var connection = mysql.createConnection({debug: ['ComQueryPacket', 'RowDataPacket']});
限制调试查询和数据包。
如果这篇文章没有帮助到你,你可以打开一个 GitHub 问题。一个好的 GitHub 问题会是这样:
- 尽可能减少代码的复制(如果可能的话)
- 尽可能调试输出你的环境信息(mysql 版本,node版本,操作系统,等等)尽你的能力去收集。
运行测试
测试套件分成两个部分:单元测试和集成测试。单元测试运行在任何机器上,而集成测试需要安装一个MySQL服务器实例。
运行单元测试
$ FILTER=unit npm test
运行集成测试
设 置环境变量MYSQL_DATABASE,MYSQL_HOST,MYSQL_PORT,MYSQL_USER 和 MYSQL_PASSWORD。MYSQL_SOCKET 可以被 MYSQL_HOST 和 MYSQL_PORT 替代连接至UNIX socket。之后运行 npm 测试。
举例来说,如果你有一个 mysql 安装被运行在 localhost:3306 并且没有为 root 用户设置密码,运行:
$ mysql -u root -e "CREATE DATABASE IF NOT EXISTS node_mysql_test" $ MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_DATABASE=node_mysql_test MYSQL_USER=root MYSQL_PASSWORD= FILTER=integration npm test
那接下来就要做下面这些事:
- 预处理子句
- 支持的编码格式不止于 UTF-8 / ASCII
本文文字及图片出自 OSchina
你也许感兴趣的:
- MariaDB消亡史
- 3700 万美元“卖身救命”,泥潭深陷的 MariaDB 准备退市
- MySQL 中引入对 JavaScript 的支持
- MySQL 支持 JavaScript,目前处于预览阶段
- 无中断升级 1200 多台 MySQL 服务器,GitHub 是如何做到的
- GitHub.com跑了1200多台MySQL主机,如何无缝升级到8.0?
- 全方位对比 Postgres 和 MySQL (2023 版)
- MySQL 小心了:MariaDB 会取代你!
- 译 | MariaDB 和 MySQL 全面对比:选择数据库需要考虑这几点
- 译 | 永远不要在MySQL中使用utf8,改用utf8mb4
你对本文的反应是: