Node系列 · ORM:mysql 驱动程序

Node系列 · ORM:mysql 驱动程序

Node 访问 MySQL 必须通过"驱动"------它把 JS 的 query() 调用翻译成 MySQL 协议包发给数据库,再把响应解析成 JS 对象。mysqlmysql2 是两个最主流的驱动,mysql2 维护更活跃、性能更好,是新项目的首选。

一、什么是驱动程序

驱动程序(Driver)是应用与数据库之间的翻译层
#mermaid-svg-G5LsgeTQDhHgxE0z{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-G5LsgeTQDhHgxE0z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-G5LsgeTQDhHgxE0z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-G5LsgeTQDhHgxE0z .error-icon{fill:#552222;}#mermaid-svg-G5LsgeTQDhHgxE0z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G5LsgeTQDhHgxE0z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-G5LsgeTQDhHgxE0z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G5LsgeTQDhHgxE0z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G5LsgeTQDhHgxE0z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-G5LsgeTQDhHgxE0z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G5LsgeTQDhHgxE0z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G5LsgeTQDhHgxE0z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-G5LsgeTQDhHgxE0z .marker.cross{stroke:#333333;}#mermaid-svg-G5LsgeTQDhHgxE0z svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G5LsgeTQDhHgxE0z p{margin:0;}#mermaid-svg-G5LsgeTQDhHgxE0z .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-G5LsgeTQDhHgxE0z .cluster-label text{fill:#333;}#mermaid-svg-G5LsgeTQDhHgxE0z .cluster-label span{color:#333;}#mermaid-svg-G5LsgeTQDhHgxE0z .cluster-label span p{background-color:transparent;}#mermaid-svg-G5LsgeTQDhHgxE0z .label text,#mermaid-svg-G5LsgeTQDhHgxE0z span{fill:#333;color:#333;}#mermaid-svg-G5LsgeTQDhHgxE0z .node rect,#mermaid-svg-G5LsgeTQDhHgxE0z .node circle,#mermaid-svg-G5LsgeTQDhHgxE0z .node ellipse,#mermaid-svg-G5LsgeTQDhHgxE0z .node polygon,#mermaid-svg-G5LsgeTQDhHgxE0z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-G5LsgeTQDhHgxE0z .rough-node .label text,#mermaid-svg-G5LsgeTQDhHgxE0z .node .label text,#mermaid-svg-G5LsgeTQDhHgxE0z .image-shape .label,#mermaid-svg-G5LsgeTQDhHgxE0z .icon-shape .label{text-anchor:middle;}#mermaid-svg-G5LsgeTQDhHgxE0z .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-G5LsgeTQDhHgxE0z .rough-node .label,#mermaid-svg-G5LsgeTQDhHgxE0z .node .label,#mermaid-svg-G5LsgeTQDhHgxE0z .image-shape .label,#mermaid-svg-G5LsgeTQDhHgxE0z .icon-shape .label{text-align:center;}#mermaid-svg-G5LsgeTQDhHgxE0z .node.clickable{cursor:pointer;}#mermaid-svg-G5LsgeTQDhHgxE0z .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-G5LsgeTQDhHgxE0z .arrowheadPath{fill:#333333;}#mermaid-svg-G5LsgeTQDhHgxE0z .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-G5LsgeTQDhHgxE0z .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-G5LsgeTQDhHgxE0z .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G5LsgeTQDhHgxE0z .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-G5LsgeTQDhHgxE0z .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G5LsgeTQDhHgxE0z .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-G5LsgeTQDhHgxE0z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-G5LsgeTQDhHgxE0z .cluster text{fill:#333;}#mermaid-svg-G5LsgeTQDhHgxE0z .cluster span{color:#333;}#mermaid-svg-G5LsgeTQDhHgxE0z div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-G5LsgeTQDhHgxE0z .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-G5LsgeTQDhHgxE0z rect.text{fill:none;stroke-width:0;}#mermaid-svg-G5LsgeTQDhHgxE0z .icon-shape,#mermaid-svg-G5LsgeTQDhHgxE0z .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G5LsgeTQDhHgxE0z .icon-shape p,#mermaid-svg-G5LsgeTQDhHgxE0z .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-G5LsgeTQDhHgxE0z .icon-shape .label rect,#mermaid-svg-G5LsgeTQDhHgxE0z .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G5LsgeTQDhHgxE0z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-G5LsgeTQDhHgxE0z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-G5LsgeTQDhHgxE0z :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} query SQL
MySQL 协议包
结果集
JS 对象
Node 应用代码
mysql2 驱动
MySQL Server

Node 调用 connection.query('SELECT ...'),驱动做三件事:

  1. 把 SQL 字符串打包成 MySQL 协议字节流
  2. 通过 TCP socket 发送给 MySQL Server
  3. 把返回的字节流解析成 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 mysql2

3.1 创建连接池

javascript:mysql-pool.js 复制代码
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:mysql-execute.js 复制代码
// 推荐:execute + 占位符
const [rows] = await pool.execute(
  'SELECT * FROM users WHERE email = ? AND status = ?',
  ['alice@example.com', 'active']
);

4.2 查询结果格式

javascript:mysql-result.js 复制代码
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:mysql-injection.js 复制代码
// ❌ 危险:字符串拼接,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

任何用户输入都不能拼进 SQLmysql2 的占位符实现是预处理语句------值在协议层和 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:mysql-best.js 复制代码
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:mysql-transaction.js 复制代码
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 状态的连接数
相关推荐
痕迹运维1 小时前
MySQL8 Docker容器化部署之C86架构(海光CPU)
mysql
程序员夏洛1 小时前
MySQL 中如果发生死锁应该如何解决?
数据库·mysql
掘金者阿豪1 小时前
极空间开启 SSH 后能做什么?从终端登录到公网远程管理完整实战
后端
肠畔码农1 小时前
Redis 深度内核解析与高性能运维调优指南
运维·数据库·redis
用户7813667114451 小时前
RGW Beast 前端流程详解
后端
智驭未来掌门人1 小时前
告别手动配置!用SDKMAN!一键管理你的所有开发工具包
后端
钱六两1 小时前
SpringAI集成RAG实操(postgresql+pgvector)中(集成到项目中)
数据库·postgresql
万维易源1 小时前
油价行情数据接口整理:国际原油与国内成品油
大数据·数据库·原油·油价
TDengine (老段)1 小时前
TDengine 如何支撑金隅集团水泥业务的能源精细化管控
大数据·数据库·物联网·能源·时序数据库·tdengine·涛思数据