MySQL Node.js 连接与使用
MySQL 是最流行的开源关系型数据库之一,而 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,将两者结合可以构建强大的后端服务。
为什么选择 MySQL + Node.js?
- MySQL 提供可靠的数据存储和管理
- Node.js 的非阻塞 I/O 模型适合数据库操作
- JavaScript 全栈开发(前后端使用同种语言)
- 丰富的 npm 生态中有许多 MySQL 相关包
安装必要的依赖
在开始之前,我们需要安装 mysql2
包,这是 Node.js 中连接 MySQL 的流行选择。
npm install mysql2
为什么选择 mysql2 而不是 mysql?
- 更好的性能
- 支持 Promise API
- 支持预处理语句
- 活跃的维护
建立数据库连接
基本连接配置
实例
const mysql = require('mysql2');
// 创建连接池(推荐生产环境使用)
const pool = mysql.createPool({
host: 'localhost', // 数据库服务器地址
user: 'root', // 数据库用户名
password: 'password', // 数据库密码
database: 'test_db', // 要连接的数据库名称
waitForConnections: true,
connectionLimit: 10, // 连接池最大连接数
queueLimit: 0
});
// 获取一个 Promise 版本的连接
const promisePool = pool.promise();
// 创建连接池(推荐生产环境使用)
const pool = mysql.createPool({
host: 'localhost', // 数据库服务器地址
user: 'root', // 数据库用户名
password: 'password', // 数据库密码
database: 'test_db', // 要连接的数据库名称
waitForConnections: true,
connectionLimit: 10, // 连接池最大连接数
queueLimit: 0
});
// 获取一个 Promise 版本的连接
const promisePool = pool.promise();
连接池 vs 单一连接
连接池优点:
- 复用连接,减少开销
- 自动管理连接生命周期
- 防止连接泄漏
- 更好的性能表现
单一连接适用场景:
- 简单脚本
- 测试环境
- 低并发应用
执行基本 CRUD 操作
查询数据(SELECT)
实例
async function getUsers() {
try {
const [rows, fields] = await promisePool.query('SELECT * FROM users');
console.log(rows);
return rows;
} catch (err) {
console.error('查询出错:', err);
throw err;
}
}
try {
const [rows, fields] = await promisePool.query('SELECT * FROM users');
console.log(rows);
return rows;
} catch (err) {
console.error('查询出错:', err);
throw err;
}
}
插入数据(INSERT)
实例
async function addUser(user) {
try {
const [result] = await promisePool.query(
'INSERT INTO users (name, email) VALUES (?, ?)',
[user.name, user.email]
);
console.log('插入ID:', result.insertId);
return result;
} catch (err) {
console.error('插入出错:', err);
throw err;
}
}
try {
const [result] = await promisePool.query(
'INSERT INTO users (name, email) VALUES (?, ?)',
[user.name, user.email]
);
console.log('插入ID:', result.insertId);
return result;
} catch (err) {
console.error('插入出错:', err);
throw err;
}
}
更新数据(UPDATE)
实例
async function updateUser(id, updates) {
try {
const [result] = await promisePool.query(
'UPDATE users SET name = ?, email = ? WHERE id = ?',
[updates.name, updates.email, id]
);
console.log('影响行数:', result.affectedRows);
return result;
} catch (err) {
console.error('更新出错:', err);
throw err;
}
}
try {
const [result] = await promisePool.query(
'UPDATE users SET name = ?, email = ? WHERE id = ?',
[updates.name, updates.email, id]
);
console.log('影响行数:', result.affectedRows);
return result;
} catch (err) {
console.error('更新出错:', err);
throw err;
}
}
删除数据(DELETE)
实例
async function deleteUser(id) {
try {
const [result] = await promisePool.query(
'DELETE FROM users WHERE id = ?',
[id]
);
console.log('删除行数:', result.affectedRows);
return result;
} catch (err) {
console.error('删除出错:', err);
throw err;
}
}
try {
const [result] = await promisePool.query(
'DELETE FROM users WHERE id = ?',
[id]
);
console.log('删除行数:', result.affectedRows);
return result;
} catch (err) {
console.error('删除出错:', err);
throw err;
}
}
高级功能与最佳实践
事务处理
实例
async function transferFunds(fromId, toId, amount) {
let connection;
try {
// 从连接池获取连接
connection = await promisePool.getConnection();
// 开始事务
await connection.beginTransaction();
// 执行转账操作
await connection.query(
'UPDATE accounts SET balance = balance - ? WHERE id = ?',
[amount, fromId]
);
await connection.query(
'UPDATE accounts SET balance = balance + ? WHERE id = ?',
[amount, toId]
);
// 提交事务
await connection.commit();
console.log('转账成功');
} catch (err) {
// 出错时回滚
if (connection) await connection.rollback();
console.error('转账失败:', err);
throw err;
} finally {
// 释放连接回连接池
if (connection) connection.release();
}
}
let connection;
try {
// 从连接池获取连接
connection = await promisePool.getConnection();
// 开始事务
await connection.beginTransaction();
// 执行转账操作
await connection.query(
'UPDATE accounts SET balance = balance - ? WHERE id = ?',
[amount, fromId]
);
await connection.query(
'UPDATE accounts SET balance = balance + ? WHERE id = ?',
[amount, toId]
);
// 提交事务
await connection.commit();
console.log('转账成功');
} catch (err) {
// 出错时回滚
if (connection) await connection.rollback();
console.error('转账失败:', err);
throw err;
} finally {
// 释放连接回连接池
if (connection) connection.release();
}
}
预处理语句
预处理语句可以提高性能并防止 SQL 注入:
实例
async function getUserById(id) {
try {
// 准备预处理语句
const [rows] = await promisePool.execute(
'SELECT * FROM users WHERE id = ?',
[id]
);
return rows[0];
} catch (err) {
console.error('查询出错:', err);
throw err;
}
}
try {
// 准备预处理语句
const [rows] = await promisePool.execute(
'SELECT * FROM users WHERE id = ?',
[id]
);
return rows[0];
} catch (err) {
console.error('查询出错:', err);
throw err;
}
}
连接池事件监听
实例
pool.on('connection', (connection) => {
console.log('新连接建立');
});
pool.on('acquire', (connection) => {
console.log('连接被获取');
});
pool.on('release', (connection) => {
console.log('连接被释放');
});
pool.on('enqueue', () => {
console.log('等待可用连接');
});
console.log('新连接建立');
});
pool.on('acquire', (connection) => {
console.log('连接被获取');
});
pool.on('release', (connection) => {
console.log('连接被释放');
});
pool.on('enqueue', () => {
console.log('等待可用连接');
});
错误处理与调试
常见错误类型
- 连接错误:数据库服务器不可达、认证失败等
- 查询语法错误:SQL 语句有误
- 约束违反:如重复主键、外键约束等
- 超时错误:查询执行时间过长
错误处理策略
实例
async function safeQuery(sql, params) {
try {
const [rows] = await promisePool.query(sql, params);
return rows;
} catch (err) {
// 根据错误类型采取不同措施
switch (err.code) {
case 'ER_DUP_ENTRY':
console.warn('重复条目:', err.sqlMessage);
throw new Error('数据已存在');
case 'ECONNREFUSED':
console.error('无法连接数据库');
throw new Error('服务不可用,请稍后再试');
default:
console.error('数据库错误:', err);
throw err;
}
}
}
try {
const [rows] = await promisePool.query(sql, params);
return rows;
} catch (err) {
// 根据错误类型采取不同措施
switch (err.code) {
case 'ER_DUP_ENTRY':
console.warn('重复条目:', err.sqlMessage);
throw new Error('数据已存在');
case 'ECONNREFUSED':
console.error('无法连接数据库');
throw new Error('服务不可用,请稍后再试');
default:
console.error('数据库错误:', err);
throw err;
}
}
}
性能优化建议
- 合理设置连接池大小:通常为 CPU 核心数的 2-3 倍
- 使用连接池而非单个连接:特别是在 Web 应用中
- 合理使用索引:加速查询性能
- 批量操作:减少往返次数
- 使用预处理语句:提高重复查询性能
- 定期释放资源:避免连接泄漏
批量插入示例:
实例
async function batchInsertUsers(users) {
const values = users.map(user => [user.name, user.email]);
try {
const [result] = await promisePool.query(
'INSERT INTO users (name, email) VALUES ?',
[values]
);
console.log('插入行数:', result.affectedRows);
return result;
} catch (err) {
console.error('批量插入出错:', err);
throw err;
}
}
const values = users.map(user => [user.name, user.email]);
try {
const [result] = await promisePool.query(
'INSERT INTO users (name, email) VALUES ?',
[values]
);
console.log('插入行数:', result.affectedRows);
return result;
} catch (err) {
console.error('批量插入出错:', err);
throw err;
}
}
安全注意事项
- 永远不要拼接 SQL 字符串:使用参数化查询防止 SQL 注入
- 限制数据库用户权限:应用账号只需必要权限
- 加密敏感数据:如密码应加盐哈希存储
- 使用 SSL 连接:生产环境建议加密连接
- 定期更新依赖:保持 mysql2 包为最新版本
完整示例项目结构
project/ ├── config/ │ └── db.js # 数据库配置 ├── models/ │ └── userModel.js # 数据模型 ├── services/ │ └── userService.js # 业务逻辑 ├── app.js # 主应用文件 └── package.json
db.js 示例:
实例
const mysql = require('mysql2');
const pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'test_db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
module.exports = pool.promise();
const pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'test_db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
module.exports = pool.promise();
点我分享笔记