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 状态的连接数
相关推荐
weixin_4604435644 分钟前
考试监控大屏为什么不能直接查数据库?WebSocket、Redis与实时数据聚合架构设计
数据库·redis·在线考试系统·培训考试系统·煤矿培训考试系统·煤矿在线考试系统·宏远培训考试系统
风哥2号1 小时前
数据库教程FGMT02‑生产环境Linux+Oracle19c安装配置与项目实战
linux·数据库·ffmpeg
怕浪猫5 小时前
FDE 如何正确解决现场项目工程性的问题
前端·后端·ai编程
九皇叔叔5 小时前
【第二章】Redis基础入门:Redis是什么、为什么快以及核心特性详解
数据库·redis·缓存
莫陌尛.5 小时前
Docker MySQL 删表卡死、执行超时(MDL锁阻塞)问题复盘与根治方案
mysql·docker·容器
云和恩墨5 小时前
删库不跑路,回滚极速触达——zData S“旁路日志”持续保护原理深剖
数据库
GreenTea5 小时前
vLLM 与 SGLang KV Cache 底层实现机制深度调研报告
前端·后端·算法
考虑考虑5 小时前
Java实现hmacsha256加密算法
java·后端·java ee
MetaLite6 小时前
SpringBoot接口分层规范-外网网关内部服务与参数边界
java·数据库·spring boot
凤山老林6 小时前
聊聊企业级 API 安全:Spring Boot 落地 OAuth 2.1、mTLS 与接口签名防篡改
spring boot·后端·安全·oauth