npm mysql插件

原文地址:https://www.npmjs.com/package/mysql
GitHub:https://github.com/mysqljs/mysql
中文文档:https://www.breword.com/mysqljs-mysql

安装

这是一个Node.js模块,通过npm注册表获得。

在安装前需要下载并安装Node.js 0.6或更高版本。

安装完Node之后,使用npm install命令:

npm install mysql

有关之前0.9版本的信息。请访问v0.9分支。

有时也可能会要求你安装 Github上的最新版本来检查bug修复是否正常,若如此,请按如下方式安装:

npm install mysqljs/mysql

简介

这是mysql数据库的node.js驱动程序,由javascript编写,无需编译,且是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();

从上例,你可以了解到以下内容:

  • 在一次连接上,每个方法都是排队并按序执行
  • 关闭连接是使用end()完成的,它能确保在向mysql服务器发送退出指令之前执行所有剩余的查询。

贡献者

感谢为该模块贡献代码的人们。这是Github贡献者页面。
另外,我要感谢以下这些人::

Andrey Hristov (Oracle) - 帮助我解决协议(protocol)的一些问题.
Ulf Wendel (Oracle) - 帮助我解决协议(protocol)的一些问题.

赞助商

balalal~~~

社区

如果你想讨论这个模块,或提出问题,请使用以下方式之一:

  • 邮件列表:https://groups.google.com/forum/#!forum/node-mysql
  • IRC:#node.js(freenode.net上,我注意任何有关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!
});

以你喜欢的方式处理错误。任何类型的连接错误(握手或网络)都被看成是致命的错误。有关详细信息,请参阅错误处理部分。

连接的配置项

建立数据库连接时,你可以配置以下选项:

  • host:要连接的数据库主机名(默认值:localhost)
  • port:连接的端口号(默认值:3306)
  • localAddress:用于TCP连接的源IP地址【可选】
  • socketPath:要连接的Unix域套接字(socket)的路径。该字段被启用时,将忽略host和port选项
  • user:MySQL用户的身份验证(即用户名)
  • password:MySQL用户密码
  • database:连接后进入的数据库名(相当于SQL的use database)【可选】
  • charset:连接时使用的字符集。这在MySQL的SQL_LEVEL中称为 “collation” - 排序规则(如utf8_general_ci),如果指定了SQL_LEVEL的字符集(如utf8mb4),则使用该字符集的默认排序规则。(默认:“UTF8_GENERAL_CI”)
  • timezone:MySQL服务器上时区配置。这用于将服务器日期/时间(date / time)值类型转换为JavaScript Date对象,反之亦然。该值是'local''Z'+HH:MM-HH:MM形式的偏移量。比如'+08:00'(默认值:‘local’)
  • connectTimeout:在初始连接到MySQL服务器时的超时毫秒数。当连接时间超过该值还未连接上,即被判为连接超时。(默认:10000)
  • stringifyObjects:true表示将对象字符串化,false表示转换为值,见#501。(默认值:false)
  • insecureAuth:允许使用旧(不安全)身份验证方法连接MySQL实例。(默认值:false)
  • typeCast:是否将列值类型转换为原生JavaScript类型。(默认值:true)
  • queryFormat:自定义query格式,是一个函数。请参阅自定义query()解析格式
  • supportBigNumbers:当处理数据库中的大数值列(BIGINT和DECIMAL类型的列)时,应该启用此选项(默认值:false)
  • bigNumberStrings:同时启用supportBigNumbersbigNumberStrings会强制大数值列(BIGINT和DECIMAL类型列)始终作为JavaScript字符串对象返回。启用supportBigNumbers但关闭bigNumberStrings将只在无法用JavaScript Number对象精确表示时(当数值不在 [-2 53 ^{53} 53,+2 53 ^{53} 53] 范围时)返回大数值作为String对象,否则它们将作为Number对象返回。如果禁用了supportbignnumbers,则忽略此选项。(默认值:false)
  • dateStrings:强制日期类型(TIMESTAMP, DATETIME, date)作为字符串返回,而不是填充到JavaScript Date对象中。值是true、false或字符串形式的类型名称数组之一。比如['TIMESTAMP'] (默认值:false)
  • debug:打印协议详细信息。值是true、false,或数据包类型名称数组。(默认值:false)
  • trace:在发生错误时生成堆栈跟踪,包括库入口的调用位置(“长堆栈跟踪”)。大量的调用会有轻微的性能损失。(默认值:true)
  • localInfile:允许LOAD DATA inffile使用LOCAL修饰符。(默认值:true)
  • multipleStatements:允许每个query()使用多个mysql语句。启用时要小心,这可能会增加SQL注入攻击的危险。(默认值:false)
  • flags:除默认连接标志外,要使用的连接标志列表。也可以将默认的列入黑名单。有关更多信息,请查看连接标志
  • ssl:一个包含ssl参数的对象,或包含SSL配置文件名称的字符串。请参见SSL配置项

除了将这些选项作为对象传递之外,还可以使用url字符串。例如:

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

注释:首先尝试将查询值解析为JSON,如果失败则假定为纯文本字符串。

SSL配置项

ssl配置项接受字符串或对象。当给定字符串时,将使用以下预定义的配置文件:

  • “Amazon RDS”:该配置文件用于连接到Amazon RDS服务器,包含来自 https://rds.amazonaws.com/doc/rds-ssl-ca-cert.pem 和 https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem 的证书

当连接到其他服务器时,你需要提供一个选项对象,格式与tls.createSecureContext相同。请注意,参数应是证书的字符串,而不是证书的文件名。下面是一个简单的例子:

var connection = mysql.createConnection({
  host:'localhost',
  ssl:{
    ca : fs.readFileSync(__dirname + '/mysql-ca.crt')
  }
});

你也可以不提供CA证书连接服务器,当然 ,不推荐这么做。

var connection = mysql.createConnection({
  host:'localhost',
  ssl:{
    // DO NOT DO THIS
    // set up your ca correctly to trust the connection
    rejectUnauthorized: false
  }
});

连接标志(flags)

如果想更改默认的连接标志,则可以使用flags选项,格式为字符串。若多个,以逗号分隔。在标志前加-,表示将该标志从默认标志中移除。直接写标志名称或者在其前加+,表示添加该到默认标志中(标志名称不区分大小写)。

var connection = mysql.createConnection({
  // 移除 FOUND_ROWS 标志, 添加 IGNORE_SPACE 标志
  flags:'-FOUND_ROWS,IGNORE_SPACE'
});

可用的表示有:

  • COMPRESS:启用协议压缩。Node.js目前不支持此功能,因此无法开启。(默认关闭)
  • CONNECT_WITH_DB:允许在连接时指定数据库。(默认开启)
  • FOUND_ROWS:将操作的行(而不是受影响的行)作为affectedRows。Send the found rows instead of the affected rows as affectedRows.(默认开启)
  • IGNORE_SIGPIPE:如果网络故障,不发出SIGPIPE。这个标志对Node.js没有影响。(默认开启)
  • IGNORE_SPACE:解析器忽略query()前的空格。(默认开启)
  • INTERACTIVE:告诉MySQL服务器这是一个“交互式”客户端。这将启用MySQL服务器上的交互式超时配置,并在进程列表中报告为交互式。(默认关闭)
  • LOCAL_FILES:可以使用 LOAD DATA LOCAL(SQL语句)。该标志由连接配置项localInfile控制。(默认开启)
  • LONG_FLAG: Longer flags in Protocol::ColumnDefinition320. (Default on)
  • LONG_PASSWORD:使用改进版本的旧密码认证。(默认开启)
  • MULTI_RESULTS:处理多个多个查询结果集。(默认开启)
  • MULTI_STATEMENTS:客户端可以在每次查询或语句准备中发送多条语句(用;分隔)。该标志由连接配置项multipleStatements控制。(默认关闭)
  • NO_SCHEMA
  • ODBC:ODBC(开放数据库互连)行为的特殊处理。这个标志对Node.js没有影响。(默认开启)
  • PLUGIN_AUTH:连接MySQL服务器时采用插件认证机制。Node.js目前不支持此功能,因此无法开启。(默认关闭)
  • PROTOCOL_41:使4.1协议。(默认开启)
  • PS_MULTI_RESULTS:可以处理多个存储过程(execute)。(默认开启)
  • REMEMBER_OPTIONS:这是专门给C端(用户端)设计的。这个标志对Node.js没有影响。(默认关闭)
  • RESERVED:启用4.1协议的旧标志。(默认开启)
  • SECURE_CONNECTION:支持native 4.1身份验证。(默认开启)
  • SSL:握手后使用SSL对传输中的数据进行加密。这个特性是通过ssl配置项控制的,因此这个标志不起作用。(默认关闭)
  • SSL_VERIFY_SERVER_CERT:在SSL设置过程中验证服务器证书。这个特性是通过SSL配置项中rejectUnauthorized选项控制的,因此这个标志不起作用。(默认关闭)
  • TRANSACTIONS:请求事务状态标志。(默认开启)

断开连接

断开mysql服务器的连接有两种方式。end()方法可以优雅的断开连接。

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

它将确保在向MySQL服务器发送COM_QUIT数据包之前,所有先前排队的操作仍然存在。如果在发送COM_QUIT包之前发生致命错误,则会向回调提供err参数,但无论如何连接都会终止。

另一种断开连接的方式是调用destroy()方法。这将立即终止底层的socket连接。另外,destroy()不再触发连接(connection)事件,并且其没有回调函数。

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()可以更方便为后续qurey操作共享连接状态。这是因为上述代码若多次调用pool.query()可能会创建多个并行运行的不同连接。下面是pool.getConnection()使用的基本结构:

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.destroy()。下次需要时连接池将会创建一个新的连接。

连接池是惰性创建连接的。如果配置连接池允许最多100个连接,但每次只同时使用5个连接,则只会建立5个连接。连接是循环式的,其从池的顶部取出,并返回到池的底部。

当从池中检索到以前的连接时,将向服务器发送一个ping包,来检查连接是否依然良好。

连接池配置项

连接池接受所有createConnection(options)的配置项。在创建池连接时,只需将配置项传递给池连接的构造函数即可。除此之外,池连接还接受一些额外的配置项:

  • acquireTimeout:连接超时时间,毫秒数。与connectTimeout略有不同,因为获取池连接并不总是涉及建立连接。如果连接请求排队,则该请求在队列中花费的时间不计入此超时。(默认10000)
  • waitForConnections:确定在没有可用连接或达到限制时池的操作。若为true,则连接请求在池中排队,并在池连接可用时在调用它。若为false,则池将立即在回调函数中返回错误。(默认true)
  • connectionLimit:池的最大连接数。(默认10)
  • queueLimit:池的排队最大数。如果设置为0,则没有限制。(默认0)

连接池事件

acquire

当从连接池中获取连接时,触发该事件。该事件被调用的时机为:该连接的所有获取连接活动被执行后,在该连接被递交给获取连接(pool.getConnection(callback))的回调函数之前。

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.getConnection(callback)的回调函数在等待可用连接时,触发该事件。

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

release

当连接被释放回池时,触发该事件。该事件被调用的时机为:该连接的所有释放活动被执行之后。因此在该事件被调用时,其连接已经为空闲状态。

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

关闭连接池的所有连接

当你使用完连接池,必须关闭所有连接,否则将使Node.js事件循环一直保持活跃状态,直到连接被NySQL服务器关闭。特别是在脚本中使用连接池,通常要执行此操作。要关闭池中所有连接,请使用pool.end()方法:

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

该方法接受一个可选的回调函数参数,你可以使用该回调函数来确定所有连接何时结束的。

一旦调用了pool.end()pool.getConnection()和其他 与连接池有关的操作将不再被执行。 可以等到所有连接都被释放(release)在调用pool.end()。如果你使用快捷的pool.query(sql,callback)方式创建池连接(而不是pool.getConnectionconnection.queryconnection.release),则在callback中调用pool.end()
pool. end会在池的每个活跃连接中调用connection. end。这将会发送一个QUIT数据包进行排队,并且设置一个标志(flag)来禁止pool. getConnection创建新的连接。所有正在执行的命令或操作都能执行完,但新的命令不会再去执行。

池集群(PoolCluster)

池集群提供多主机连接。(分组 & 重试 & 选择器)(group & retry & selector)

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

PoolCluster配置项

  • canRetry:若为true,PoolCluster将会在连接失败后尝试再次连接。(默认true)
  • removeNodeErrorCount:若连接失败,节点的errorCount会自增。当errorCount大于removeNodeErrorCount时,移除PoolCluster中的一个节点。(默认5)
  • restoreNodeTimeout:若连接失败,则指定下一次连接尝试的间隔时间,毫秒数。若设置为0,则节点将被删除,并且不再被重用。(默认0)
  • defaultSelector:默认选择器。(默认:RR)
    • RR: 交替选择. (Round-Robin)
    • RANDOM:通过随机函数选择节点
    • ORDER:选择第一个无条件可用的节点
var clusterConfig = {
  removeNodeErrorCount: 1, // Remove the node immediately when connection fails.
  defaultSelector: 'ORDER'
};
 
var poolCluster = mysql.createPoolCluster(clusterConfig);

切换用户和改变连接状态

MySQL提供了一个改变用户的命令,它允许你在不关闭底层socket的情况下,更改当前用户和连接的其他状态:

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

接受的配置项:

  • user:新用户名(默认为前一个)
  • password:新用户密码(默认为前一个)
  • charset:新的字符集(默认为前一个)
  • database:新的数据库名(默认为前一个)

这个功能的一个有用的副作用是,该函数还会重置一些连接状态(变量、事务等)。

在此操作期间遇到的错误将被此模块视为致命连接错误。

服务器失去连接

由于网络问题、服务器超时、服务器重新启动或崩溃,您可能会与MySQL服务器失去连接。所有这些事件都被认为是致命错误,并会有err被写入相应的会回调函数,code = 'PROTOCOL_CONNECTION_LOST'。相关详情信息,请参阅错误处理部分。

重连是通过建立新连接来完成的。一旦终止,现有的connection对象会丢失。

断开的池(Pool)连接将从池中删除,从而为下次getConnection调用时创建的新连接释放空间。

断开的集群(PoolCluster)连接将作为相关节点的错误计数,并增加该节点的错误代码。一旦给定节点上的错误超过removeNodeErrorCount,它就会从集群中删除。发生这种情况时,如果不再有任何与模式匹配的节点,PoolCluster可能会发出POOL_NONEONLINE错误。通过restoreNodeTimeout配置可以设置给定超时后恢复脱机节点。

执行数据库查询

执行数据库查询最基础的方式就是在相应对象上调用其query()方法(比如Connection对象,Pool对象或PoolNamespace对象)

query()最基本的格式是query(sqlString, callback),其中sqlString是字符串类型的SQL语句,callback是回调函数:

connection.query('SELECT * FROM `books` WHERE `author` = "David"', function (error, results, fields) {
  // error 如果在查询期间发生错误,error将是Error对象(如果有)
  // results 将是查询结果(如果有)
  // fields 将包含有关返回结果字段(如果有)的信息
});

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),你可以为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)
});

注意,第二种和第三种格式可以混搭在一起:占位符的值是通过values参数传递而非options参数。
values参数的值将会覆盖options.values

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的NO_BACKSLASH_ESCAPES模式被禁用时有效(MySQL服务器的默认状态)。
为避免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;
  // ...
});

有多个占位符时,其与参数values数组同位置的值一一映射。比如,下面的query语句中,foo = abar = bbaz = cid = 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()将values参数的值进行转义了。

注意:与预处理语句不同的是,?就算出现在注释或字符串里,也会被替换成对应的值。

不同类型的值会按不同的规则进行转义,规则如下:

  • 数字类型保持不变
  • 布尔类型被转换为true / false
  • Date对象被转换为 'YYYY-mm-dd HH:ii:ss' 字符串
  • Buffer对象被转换为十六进制字符串
  • 字符串类型会进行安全转义
  • 数组被转换成list,比如['a', 'b']被转换成'a', 'b'
  • 嵌套数组被转换成分组list(用于批量插入),比如:[['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()。创建的对象就算使用?占位符也不会被更改。如果你将函数用作动态值传入,这一点很有用:

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

注意:传给mysql.raw()的字符串会忽略所有转义函数,因此在传递未经验证的输入时要小心。
如果你想自己转义,也可以直接使用转义函数:

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

转义查询标识符(identifier)

如果你不信任用户提供的SQL标识符(比如数据库名、表名、列名),可以使用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 (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`

如果不想把.当成限定标识符,可以设置escapeId()的第二个参数为true,以将字符串当作文本标识符(literal identifier)来处理:

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注入对象属性。

生成query语句

你可以运用标识符转义,并使用mysql.format来生成一个具有多个插入点的query语句,例如:

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

由此,你将获得一个合法并已转义的query语句。然后你可以安全地将其发送到数据库。如果你想查看发送数据库前的query语句,mysql.format会是很有用的。mysql.format是通过SqlString.format实现的。您还可以选择(但不是必需的)传入stringifyObjectandtimeZone,这将允许你提供将对象转换为字符串的自定义方法,以及location-specific / timezone-aware的Date。

自定义query()解析格式

如果想修改query()的解析模式,可以使用connection的配置项去编写自定义格式的方法。如果想使用内置的escape()或任何其他connection函数,可以访问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" });
//UPDATE posts SET title = `Hello MySQL`

获取插入行ID

如果你向auto increment primary key的表插入了一行数据,你可以这样获取插入行ID:

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

当处理大数值(高于JavaScript数字精度限制)时,您应该考虑启用supportBigNumbers配置项,以便能够将插入id作为字符串读取。否则将抛出错误。

当从数据库中获取大数值时也需要此配置项,否则,由于精度限制,您将获得舍入到数百或数千的值。

获取受影响的行(affected rows)的数量

使用insertupdatedelete语句时,可以获取到受影响行的数量:

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

获取修改的行(changed rows)的数量

使用update语句时,可以获取到修改行的数量。

changedRowsaffectedRows的不同之处在于,值未更改的更新行不被列入changedRows

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

获取连接ID

你可以通过一个给定连接的threadId属性获取到MYSQL的连接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()方法提供回调函数参数
  • 如果INSERT / UPDATE 成功,新旧两行的’result’事件都会被触发。
  • 不要暂停(pause)太长时间,否则会抛出错误Error: Connection lost: The server closed the connection.。MySQL服务器上的netwritetimeout setting选项可以让你配置该时间限制。

另外,你可能想知道为什么当前无法流式处理单个行的所有列,当前它们会被整个地存入缓冲区。如果你确实有这方面使用场景,请告诉我(作者),同时也欢迎贡献代码。

利用流的pipe处理查询数据

query对象提供了stream([options])方法,它将query事件包裹到了可读流( Readable Stream)对象。根据下游拥塞情况和配置项highWaterMark,其可以很容易将数据引流下来,并实现了自动开 / 关(resume / pause)。stream的objectMode选项值被设置为true且无法更改(如果你需要字节流,则需要使用转换流,例如转换成对象流)。

例如,将查询结果传递到另一个流中变得很简单(该流的最大缓冲区为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)时,可能会得到列名重叠的结果集。

默认情况下,其按照从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: '...',
  }, ...]
  */
});

事务(transaction)

我们在连接层(connection level)提供了简单的事务支持:

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()只是分别执行START TRANSACTIONCOMMITROLLBACK命令的函数。重要的是要理解MySQL中的许多命令可以导致隐式提交,更多信息,可查阅MySQL文档。

Ping

使用connection.ping()方法可将ping包发送出去。该方法向服务器发送一个ping包,当服务器响应时,将触发回调函数。如果发生错误,错误信息将被发送回调函数的error参数:

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

超时时间

每个操作都有一个可选的超时配置项。这允许你为操作指定适当的超时时间。重要的是要注意,这些超时时间不是MySQL协议的一部分,而是客户端自己的超时机制。这意味着超时发生时,连接会被终止,进一步的操作无法执行。

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:String。若是MySQL服务器错误,则使用MySQL服务器错误符号(如,'ER_ACCESS_DENIED_ERROR');若是Node.js错误,则使用Node.js的错误代码(如,'ECONNREFUSED');另外还有内部错误代码(如,‘PROTOCOL_CONNECTION_LOST’
  • err.errno:Number。MySQL服务器错误编号。只有MySQL服务器错误时,其才有值
  • err.fatal:Boolean。表明此错误是否会终止连接。如果错误不是MySQL本身导致的,此属性不会被定义。
  • err.sql:String。保存了发生错误的SQL语句。这对于使用更高级别的接口(例如,生成查询的ORM)很有用。
  • err.sqlState:String。包含五个字符的SQLSTATE值。仅来自MySQL服务器错误。
  • err.sqlMessage:String。包含描述错误的消息字符串。仅来自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 (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
});

如果发生致命错误并且没有挂起的回调,或者发生正常错误但没有属于其回调的,则该错误将作为连接对象上的’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.js 中很特殊。如果它们没有对应的监听对象,错误发生地的堆栈信息会被打印出来,进程也将被杀死。

不要忽略错误。你应该为错误提供回调。如果你实在不想处理错误,可以参考下面的做法:

// 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
注意:二进制字符集中的文本作为Buffer返回,而不是字符串返回。

  • CHAR
  • VARCHAR
  • TINYTEXT
  • MEDIUMTEXT
  • LONGTEXT
  • TEXT
  • ENUM
  • SET
  • DECIMAL(可能超过浮点精度)
  • BIGINT(可能超过浮点精度)
  • TIME(可能会映射为Date,但是对应的日期值是什么呢?)
  • GEOMETRY(从来没用过,如果你用过就联系我)

不建议禁用类型转换(将来可能会取消/更改),但您目前可以在连接上这样做::

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

或在query级别上:

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

自定义类型转换

也可以通过函数自定义类型转换,该函数会将一些列信息作为参数,比如数据库、表、列、列类型和列预定的大小。如果你仅想对某些类型进行转换,也可以通过该函数实现。

该函数提供两个参数:fieldnext。通过field对象触发解析器函数,并为特定字段返回值。

field参数是一个对象,其包含了一些属性,如下:

  • db - String,该列所属数据库名
  • table - String,该列所属的表名
  • name - String,该列的列名
  • type - String,该列的类型名(全大写)
  • length - Number,数据库指明的该列占用存储的大小

next参数是一个函数,当调用它时,将返回给定列的默认类型转换。

field对象也包含了以下方法:

  • string() - 将该列的值解析成字符串
  • buffer() - 将该列的值解析成Buffer
  • geometry() - 将该列的值解析成几何值(parse the field as a geometry value)

MySQL协议是一个基于文本的协议。这就意味着,在网络传输上,所有列的类型都是以字符串形式呈现的,这就是为什么field对象的方法都是字符串相关的。根据类型信息(如INT),(列值)字符串会被强制转换为不同的Javascript类型(如数字)。

下面是一个将 TINYINT(1) 强制转换为 Boolean 的例子:

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

警告:在你自定义的类型转换回调函数里,要想触发解析器函数,你必须调用 field 的三个方法之一。而且,这三个方法都只能被调用一次。

调试和报告问题

如果你遇到了问题,为连接启用调试模式可能会有帮助到你:

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

这将在标准输出(stdout)上打印所有传入和传出的数据包。你也可以通过传递一个类型数组来限制对数据包类型的调试:

//将调试限制在查询和数据包上
var connection = mysql.createConnection({debug: ['ComQueryPacket', 'RowDataPacket']});

安全问题

安全问题不应该首先通过GitHub或其他公共论坛报告,而是保密,以便合作者评估报告并(a)设计修复并计划发布日期或(b)断言它不是安全问题(在这种情况下,它可以在公共论坛上发布,如GitHub问题)。

主要的私人论坛是电子邮件,通过电子邮件发送模块的作者或打开GitHub问题,简单地询问应该向谁解决安全问题,而不披露问题或问题类型。

理想的报告应该包括安全问题是什么以及如何利用安全问题的明确指示,理想情况下还应附带概念证明(PoC),以便协作者进行工作并验证潜在的修复。

贡献代码

该项目欢迎社区的贡献。你可以使用GitHub pull request。如果你不知道怎么创建GitHub pull request,请参阅GitHub文档:Creating a pull request。

pull request要求:

  1. 详细描述你的pull request。应包括“内容”和“原因”。
  2. 测试应尽可能地通过。详见运行测试部分。安全起见, GitHub也会自动运行测试。
  3. 更改部分应包含测试。新功能有对应的测试,错误修复也应该有对应的测试(如果代码不做任何变化,测试不通过,但应用错误修复的代码后,该测试通过。)运行npm run test-cov来生成coverage/文件夹,文件夹里是包含代码覆盖率的HTML页面,通过这些HTML页面,你可以更好地了解所添加的内容是否被纳入测试范围。
  4. 如果你的pull request是一项新功能,你应该在Readme.md里详细说明。
  5. 为了保证代码样式一致,请运行npm run lint,并尽可能修复不符合lint规则的地方。

运行测试

测试套件分为两个部分:单元测试和集成测试。单元测试可以在任何计算机上运行,而集成测试则需要设置MySQL服务器实例。

运行单元测试

FILTER=unit npm test

运行集成测试

设置环境变量MYSQL_DATABASEMYSQL_HOSTMYSQL_PORTMYSQL_USERMYSQL_PASSWORDMYSQL_SOCKET可以代替MYSQL_HOSTMYSQL_PORT,这样,直接与 UNIX socket连接。运行npm test。

例如,如果你安装了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

Todo

  • 准备好的语句
  • 支持UTF-8 / ASCII 以外的其他编码

你可能感兴趣的:(mysql,node.js)