Skip to content

MySQL - Node.js 语法

Node.js 是一个强大的 JavaScript 运行时,用于构建快速且可伸缩的服务器端应用程序。一个常见的需求是将这些应用程序连接到 MySQL 数据库。本教程将指导您了解 Node.js 与 MySQL 集成的现代最佳实践,重点关注安全性、性能和可维护性。

虽然存在多个库,但 mysql2 是现代 Node.js 应用程序的推荐选择。它是旧版 mysql 库的直接替代品 (drop-in replacement),但提供了显著优势:

  • 性能: mysql2 显著更快。
  • 现代 JavaScript: 完全支持 Promises 和 async/await,这有助于避免“回调地狱 (callback hell)”并生成更清晰的代码。
  • 安全性: 对预处理语句 (Prepared Statements) 提供强大支持,以防止 SQL 注入 (SQL injection) 攻击。
  • 功能: 支持压缩、SSL、流 (streaming) 等。

首先,让我们设置一个新的 Node.js 项目。

# 1. 创建一个新的项目目录
mkdir node-mysql-app
cd node-mysql-app
# 2. 初始化一个 Node.js 项目
npm init -y
# 3. 安装 mysql2 库
npm install mysql2

对于生产应用程序,为每个查询创建新连接效率低下。最佳实践是使用连接池 (connection pool),它管理一组可重用的活动连接。

让我们创建一个名为 db.js 的文件来管理数据库连接。我们将使用基于 Promise 的 API 来编写现代异步代码。

// db.js
const mysql = require('mysql2');
// 硬编码凭据存在安全风险。
// 在实际应用中,请使用环境变量(例如,结合 `dotenv`)。
const pool = mysql.createPool({
host: 'localhost', // or your db host
user: 'your_user',
password: 'your_password',
database: 'your_database',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// 导出 Promise 封装的连接池
module.exports = pool.promise();

步骤 3:使用 async/await 执行查询

Section titled “步骤 3:使用 async/await 执行查询”

为防止 SQL 注入,您绝不应将用户输入直接拼接到 SQL 字符串中。始终使用预处理语句 (prepared statements),其中使用占位符 (?) 来表示值。mysql2 库会处理安全的替换。

// app.js
const pool = require('./db');
async function main() {
let connection;
try {
// 从连接池获取一个连接
connection = await pool.getConnection();
console.log('Connected to MySQL database!');
// --- 示例 1:获取数据 ---
const [rows, fields] = await connection.execute('SELECT * FROM `users` WHERE `id` = ?', [1]);
console.log('User found:', rows[0]);
// --- 示例 2:插入数据 ---
const newUser = { name: 'Jane Doe', email: 'jane.doe@example.com' };
const [insertResult] = await connection.execute(
'INSERT INTO `users` (`name`, `email`) VALUES (?, ?)',
[newUser.name, newUser.email]
);
console.log('New user created with ID:', insertResult.insertId);
} catch (err) {
console.error('Database error:', err);
} finally {
// 始终将连接释放回连接池
if (connection) connection.release();
}
}
main().then(() => {
// 应用程序关闭时关闭连接池
pool.end();
});

对于必须全部成功或全部失败的操作(例如转账),您必须使用事务。

async function transferFunds(fromId, toId, amount) {
let connection;
try {
connection = await pool.getConnection();
await connection.beginTransaction();
// 1. 从发送方扣款
await connection.execute('UPDATE accounts SET balance = balance - ? WHERE id = ?', [amount, fromId]);
// 2. 向接收方加款
await connection.execute('UPDATE accounts SET balance = balance + ? WHERE id = ?', [amount, toId]);
// 如果两个操作都成功,则提交事务
await connection.commit();
console.log('Transfer successful!');
} catch (err) {
// 如果发生任何错误,则回滚整个事务
if (connection) await connection.rollback();
console.error('Transaction failed:', err);
throw err; // 重新抛出错误以便调用方处理
} finally {
if (connection) connection.release();
}
}
  • ECONNREFUSED: 数据库服务器未运行或无法通过给定的 host 和 port 访问。
  • ER_ACCESS_DENIED_ERROR: user 或 password 不正确。
  • ER_NO_SUCH_TABLE: 您查询的表在指定的 database 中不存在。
  • Connection is not defined: 确保仅在成功获取连接后才释放它。finally 块对此至关重要。