应选用 mysql2 而非 mysql,因其原生支持 .promise() 方法,自动返回 Promise 且正确管理连接池生命周期;mysql 官方包无此能力,强行 promisify 会丢失连接上下文。

直接用 mysql2 的 .promise() + async/await 就能实现干净的异步查询,不需要额外 Promise 包裹或手动管理连接释放——前提是连接池配置正确、SQL 参数化到位、错误不被吞掉。
为什么不能直接用 mysql 而要选 mysql2
mysql 官方包不支持原生 Promise,强行 util.promisify 会丢失连接池上下文,导致 query 调用后连接未归还;mysql2 内置 .promise() 方法,返回的 pool 实例所有方法(query、execute)都自动返回 Promise,且连接生命周期由池统一管理。
- 必须安装
mysql2:运行npm install mysql2 --save - 不要 require
mysql,否则即使装了mysql2也会因缓存导致模块错乱 -
mysql2默认开启 prepare 协议,对参数化查询更安全,但旧版 MySQL 5.7 可能需显式关闭:flag: 'NO_PREPARE'
createPool().promise() 封装时的关键配置项
连接池不是“配了就行”,几个关键参数直接影响高并发下的稳定性:
-
waitForConnections: true—— 必须设为true,否则连接耗尽时直接抛错,而不是排队等待 -
connectionLimit: 10—— 初始建议 10~20,低于 Express 默认的 50 并发数,避免 DB 端被打满 -
queueLimit: 0—— 设为0表示无上限排队,配合waitForConnections使用 -
acquireTimeout: 10000—— 获取连接超时设为 10s,避免请求卡死在连接阶段 -
charset: 'utf8mb4'—— 必须显式指定,否则 emoji 和部分中文可能乱码
示例配置片段:
立即学习“前端免费学习笔记(深入)”;
const pool = mysql2.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 15,
queueLimit: 0,
acquireTimeout: 10000,
charset: 'utf8mb4'
});
<p>module.exports = pool.promise();Express 路由中调用封装好的查询时的常见陷阱
看似简单的 await pool.query(...),实际容易踩三类坑:
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
- 没做参数化:拼接 SQL 字符串(如
`SELECT * FROM users WHERE id = ${req.params.id}`)→ 直接导致 SQL 注入,必须用 ? 占位符 + 数组传参 - 没处理空结果:查询返回
[]或undefined时,后续逻辑未判空 → 报Cannot read property 'xxx' of undefined - 错误未透出:在
try/catch中只console.error却没res.status(500).json(...)→ 前端收不到错误,一直 loading
正确写法示例:
app.get('/user/:id', async (req, res) => {
try {
const [rows] = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
if (rows.length === 0) return res.status(404).json({ error: '用户不存在' });
res.json(rows[0]);
} catch (err) {
console.error(err);
res.status(500).json({ error: '数据库查询失败' });
}
});事务封装:用 beginTransaction + commit + rollback 组合
单条语句不用事务,但涉及多表更新、余额扣减、状态流转等场景,必须用事务。别自己手写 connection.beginTransaction() 嵌套——容易漏 release 或 rollback。
推荐封装一个 execTransaction 工具函数,接收一个 async 回调,内部自动处理连接获取、开启事务、提交/回滚、连接释放:
const execTransaction = async (fn) => {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
const result = await fn(connection);
await connection.commit();
return result;
} catch (err) {
await connection.rollback();
throw err;
} finally {
connection.release();
}
};使用时:
app.post('/order', async (req, res) => {
try {
await execTransaction(async (conn) => {
await conn.query('INSERT INTO orders (...) VALUES (?)', [orderData]);
await conn.query('UPDATE inventory SET stock = stock - ? WHERE id = ?', [qty, itemId]);
});
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});注意:事务内所有 query 必须用传入的 conn 实例,不能用全局 pool.query,否则不在同一事务上下文中。

















