游客发表

nodejs数据库

发帖时间:2026-10-17 05:07:21

MySQL 的数据 Node.js 驱动装起来只要一行命令,但真正踩过坑的数据人知道,这玩意儿的数据问题从来不在安装上。我见过太多项目在本地跑得好好的数据,一上生产环境就报 Too many connections,数据然后开发者一脸茫然地重启服务——这根本不是数据在解决问题,是数据在赌运气。

Node.js 操作数据库的数据核心矛盾在于:它的异步模型和数据库连接的生命周期天然不匹配。一个 HTTP 请求进来,数据你查一次库,数据连接建立、数据查询、数据关闭,数据看起来没问题。数据但 QPS 一上来,数据连接创建和销毁的开销直接把 CPU 吃满。所以连接池不是可选项,是必选项。

const mysql = require('mysql2/promise'); const pool = mysql.createPool({  host: 'localhost', user: 'root', database: 'test', waitForConnections: true, connectionLimit: 10, queueLimit: 0 });

这段代码几乎每篇教程都会贴。但 connectionLimit: 10 到底意味着什么?意味着同时最多有 10 个连接在工作,第 11 个请求进来时,它会排队等。如果队列满了——默认 queueLimit: 0 表示无限排队——内存就会涨。我见过一个项目连接池设了 5,结果定时任务和 API 共用这个池子,定时任务跑批的时候 API 全部超时。排查了半天,最后发现是池子太小。

用 mysql2 而不是 mysql,理由很直接:mysql2 支持 Promise,原生 mysql 的回调写法在 async/await 时代就是给自己找麻烦。而且 mysql2 的性能更好,对预处理语句的支持也更完整。这个选择没什么好犹豫的。

预处理语句值得单独说。拼接 SQL 字符串是安全事故的温床,但很多人不知道的是,即使用参数化查询,也有坑:

// 错误示范 const sql = `SELECT * FROM users WHERE id = ${ userId}`; // 正确做法 const [rows] = await pool.execute('SELECT * FROM users WHERE id = ?', [userId]);

注意这里用的是 execute 而不是 query。区别在于 execute 会走预处理语句协议,query 只是做了转义。对于需要重复执行的 SQL,execute 在数据库端会缓存执行计划,性能更好。但有个细节:execute 对某些数据类型(比如 IN 子句里的数组)支持不好,这时候还是得用 query 加手动转义。

另一个真实场景:事务。Node.js 里写事务最怕的就是忘记释放连接。正确的写法是从池子里拿一个连接,用完必须 release:

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

finally 块里的 release 是保命的。漏掉它,连接池很快就会被耗尽,然后整个服务挂掉。我就因为这个 bug 在凌晨两点被叫起来过。

MongoDB 的情况不太一样。它不需要连接池配置得那么精细,因为驱动自己管得不错。但 Mongoose 的 bufferCommands 默认开启,意味着数据库没连上时,查询会缓存起来而不是直接报错。这在开发时很方便,在生产环境却可能让你误以为一切正常,直到缓存堆积把内存撑爆。建议生产环境关掉它。

选型上,关系型数据用 MySQL + mysql2,文档型用 MongoDB + 原生驱动就够了。ORM 不是必需品,尤其当你的 SQL 稍微复杂一点,ORM 生成的查询往往比你手写的慢一个数量级。我个人的习惯是:简单 CRUD 可以用 ORM 省事,复杂查询直接写 SQL。

    热门排行

    友情链接