Node系列 · ORM:mysql 驱动程序
Node 访问 MySQL 必须通过"驱动"——它把 JS 的
query()调用翻译成 MySQL 协议包发给数据库,再把响应解析成 JS 对象。mysql和mysql2是两个最主流的驱动,mysql2维护更活跃、性能更好,是新项目的首选。
一、什么是驱动程序
驱动程序(Driver)是应用与数据库之间的翻译层:
Node 调用 connection.query('SELECT ...'),驱动做三件事:
- 把 SQL 字符串打包成 MySQL 协议字节流
- 通过 TCP socket 发送给 MySQL Server
- 把返回的字节流解析成 JS 数组 / 对象
二、mysql vs mysql2
| 维度 | mysql | mysql2 |
|---|---|---|
| 维护状态 | 维护较少 | 持续活跃 |
| 性能 | 一般 | 更快(原生绑定 + 流优化) |
| Promise 支持 | 需要 mysql2/promise 包装 | 内置 mysql2/promise |
| 预处理语句 | 支持 | 支持且更标准 |
| 认证 | 较少 | 支持 caching_sha2_password(MySQL 8 默认) |
| 推荐 | 老项目 | 新项目首选 |
TIP
MySQL 8.0 默认认证插件是 caching_sha2_password,老版本 mysql 驱动不支持,会报"Client does not support authentication protocol"。用 mysql2 解决。
三、安装与连接
bash
npm install mysql23.1 创建连接池
javascript
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: '127.0.0.1',
port: 3306,
user: 'root',
password: 'your-password',
database: 'myapp',
waitForConnections: true, // 池满时等待(true)还是报错(false)
connectionLimit: 10, // 最大连接数
queueLimit: 0, // 等待队列上限,0 = 不限
});
// 使用
const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [1]);
console.log(rows);3.2 连接池 vs 单连接
| 维度 | 连接池 | 单连接 |
|---|---|---|
| 并发请求 | 多连接并行 | 串行排队 |
| 性能 | 高(建连接成本被复用) | 低(每次要建/拆 TCP) |
| 复杂度 | 需要配置 limit | 简单 |
| 适用 | Web 服务等高并发 | 一次性脚本 |
TIP
生产环境永远用连接池。每次 pool.query() 自动从池里取连接、用完归还;连接复用减少 TCP 握手开销。
四、查询 API
4.1 query vs execute
| 方法 | 是否预处理 | 防 SQL 注入 | 性能 | 何时用 |
|---|---|---|---|---|
query(sql, params) | 可选(参数化时是) | ✅ | 一般 | 简单查询 |
execute(sql, params) | ✅ 强制预处理 | ✅ 更安全 | 更快(MySQL 预编译) | 高频重复 SQL |
javascript
// 推荐:execute + 占位符
const [rows] = await pool.execute(
'SELECT * FROM users WHERE email = ? AND status = ?',
['alice@example.com', 'active']
);4.2 查询结果格式
javascript
const [rows, fields] = await pool.execute('SELECT id, name FROM users LIMIT 3');
// rows:行数据数组
// [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, ...]
// fields:列元信息(一般用不到)
// [{ name: 'id', type: 3, ... }, { name: 'name', type: 253, ... }]五、预处理语句防 SQL 注入
永远用占位符,不要拼接字符串:
javascript
// ❌ 危险:字符串拼接,SQL 注入风险
const userId = req.query.id; // 攻击者传 "1 OR 1=1"
const sql = `SELECT * FROM users WHERE id = ${userId}`;
await pool.query(sql);
// ✅ 安全:占位符
const sql = 'SELECT * FROM users WHERE id = ?';
await pool.query(sql, [userId]);DANGER
任何用户输入都不能拼进 SQL。mysql2 的占位符实现是预处理语句——值在协议层和 SQL 分离,攻击者无法通过输入改变 SQL 结构。
六、连接池参数调优
| 参数 | 默认值 | 调优建议 |
|---|---|---|
connectionLimit | 10 | 按 MySQL max_connections 和应用并发量决定;一般 10-30 |
waitForConnections | true | 高并发设 true(排队等待) |
queueLimit | 0 | 0 = 不限;生产建议设一个上限(如 100),避免请求堆积 |
idleTimeout | 60000ms | 空闲连接回收时间 |
enableKeepAlive | false | 高并发场景设 true,TCP keep-alive |
七、连接管理最佳实践
javascript
const pool = mysql.createPool({ /* config */ });
// ✅ 推荐:通过 pool 自动管理
const [rows] = await pool.query('SELECT ...');
// ❌ 不推荐:手动创建/关闭连接
const conn = await mysql.createConnection({ /* config */ });
await conn.query('SELECT ...');
await conn.end(); // 容易忘记关闭导致连接泄漏WARNING
连接泄漏——忘记 connection.end() 是最常见的资源泄漏。每次泄漏一个连接,MySQL SHOW PROCESSLIST 会看到一堆 Sleep 状态的连接,最终 max_connections 满。
八、事务
javascript
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
await conn.execute('UPDATE accounts SET balance = balance - ? WHERE id = ?', [100, 1]);
await conn.execute('UPDATE accounts SET balance = balance + ? WHERE id = ?', [100, 2]);
await conn.commit();
} catch (err) {
await conn.rollback();
throw err;
} finally {
conn.release(); // 归还连接到池
}注意:getConnection() 拿到的连接用完必须 release()(不是 end())。end() 会销毁连接,而 release() 只是归还到池。
九、常见错误
| 错误 | 原因 | 解决 |
|---|---|---|
ECONNREFUSED | 连接被拒(端口 / 防火墙) | 检查 MySQL 监听端口、防火墙 |
ER_ACCESS_DENIED_ERROR | 密码错 | 重置 root 密码 |
ER_BAD_DB_ERROR | 数据库不存在 | CREATE DATABASE 或改 database 字段 |
PROTOCOL_CONNECTION_LOST | 连接断了 | 启用 enableKeepAlive: true |
ER_DUP_ENTRY | 唯一键冲突 | 检查数据或用 INSERT ... ON DUPLICATE KEY UPDATE |
| 连接池耗尽(卡死) | 太多未释放的连接 | 用 pool.query 而非手动 getConnection |
十、小结
mysql2是新项目首选驱动;性能更好、Promise 原生支持、兼容 MySQL 8- 永远用连接池(
createPool),避免每次查询都建/拆连接 - 永远用占位符(
?),不要拼接 SQL——防注入是底线性要求 execute()走预处理协议,比query()更快更安全- 事务用
getConnection()+beginTransaction/commit/rollback,用完必须release() - 连接管理不当会导致连接泄漏;监控
SHOW PROCESSLIST看 Sleep 状态的连接数
