一、前置准备:明确技术边界与环境要求
Node.js本身不内置MySQL驱动,它提供的是服务端JavaScript运行时。要让Node.js与MySQL通信,必须借助第三方驱动包。mysql是Node.js生态中使用时间较长、文档较完善的驱动之一,基于回调方式处理异步操作。它的特点是稳定、兼容性好,适合维护旧项目或对回调风格没有抵触的团队。如果新项目追求Promise与async/await的写法,可以优先考虑mysql2,它兼容mysql的大部分API,同时原生支持Promise。本教程以mysql包为主线,因为理解回调模式后再迁移到Promise风格并不困难。
开始之前,确认以下环境已经就绪:
-
Node.js已安装,终端中
node-v与npm-v能正常输出版本号。 -
MySQLServer已安装并处于运行状态。
-
一个代码编辑器,例如VisualStudioCode。
-
具备JavaScript基础与SQL基本语句的读写能力。
二、初始化项目与安装驱动
新建项目目录并进入:
mkdir mysql-demo
cd mysql-demo
初始化package.json,用于记录项目信息与依赖:
npm init -y
安装MySQL驱动:
npm install mysql
安装完成后,package.json的dependencies中会出现mysql字段,node_modules目录也会生成。这一步看似简单,但有一个经验点值得注意:不要全局安装MySQL驱动。数据库连接属于项目级依赖,全局安装会导致不同项目之间版本冲突,也不利于团队协作时锁定版本。
三、创建数据库与连接配置
在Node.js连接之前,先在MySQL中创建目标数据库。打开MySQLWorkbench或命令行:
CREATE DATABASE companydb;
SHOW DATABASES;
确认companydb出现在数据库列表中。接着在项目根目录创建app.js,引入驱动并配置连接:
const mysql = require('mysql');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'your_password',
database: 'companydb',
port: 3306
});
连接参数说明:
| 参数 | 说明 |
|---|---|
| host | MySQL服务器主机名 |
| user | MySQL用户名 |
| password | 对应用户的密码 |
| database | 要连接的数据库名称 |
| port | MySQL服务端口,默认3306 |
这里有一个容易忽略的细节:password不要直接硬编码在业务代码中。实际项目应通过环境变量或配置文件注入,例如使用dotenv读取.env文件。硬编码密码一旦提交到代码仓库,数据库凭证就会泄露。
四、建立连接与执行SQL查询
配置完成后,调用connection.connect()建立连接:
connection.connect((err) => {
if (err) {
console.error('Connection Failed:', err.message);
return;
}
console.log('Connected to MySQL Successfully');
});
运行:
node app.js
终端输出ConnectedtoMySQLSuccessfully表示连接成功。接着可以执行一条简单查询验证链路:
connection.query(
'SELECT NOW() AS CurrentTime',
(err, result) => {
if (err) {
console.log(err);
return;
}
console.log(result);
}
);
输出格式类似:
[ { CurrentTime: 2026-09-09T08:30:00.000Z } ]
这条查询的意义在于确认三件事:连接对象可用、SQL能发送到MySQL、结果能回传到Node.js回调。项目中,建议把这类验证查询放在开发初期完成,不要等到业务表建好后再排查连接问题。
五、创建表与数据操作
连接稳定后,可以执行建表语句:
const sql = `
CREATE TABLE employees(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
salary DECIMAL(10,2)
)`;
connection.query(sql, (err) => {
if (err) throw err;
console.log('Table Created Successfully');
});
建表之后,增删改查都通过connection.query()完成。下面给出一个插入与查询的组合示例,文件名可命名为code_01_crud.js:
connection.query(
'INSERT INTO employees(name, salary) VALUES (?, ?)',
['code_01_张三', 8800.00],
(err, result) => {
if (err) throw err;
console.log('Inserted ID:', result.insertId);
connection.query(
'SELECT * FROM employees WHERE id = ?',
[result.insertId],
(err, rows) => {
if (err) throw err;
console.log(rows);
}
);
}
);
使用?占位符而不是字符串拼接,是防止SQL注入的基本手段。mysql包会自动对参数进行转义。把用户输入直接拼进SQL字符串,等于把数据库暴露给外部输入。
六、错误处理与常见连接故障
连接失败时,err.message会给出线索。以下是三类高频错误:
Accessdeniedforuser'root'@'localhost'
用户名或密码不正确,或者该用户没有从当前主机连接的权限。检查MySQL用户权限表,确认root是否允许localhost访问。
Unknowndatabase'companydb'
数据库不存在。连接参数中的database必须与MySQL中已创建的库名一致。
connectECONNREFUSED
MySQL服务未启动或端口不可达。确认MySQLServer服务正在运行,且port配置与实际监听端口一致。
在回调中统一处理错误,比在每个查询里写if(err)throwerr更利于维护。生产环境应把错误记录到日志系统,而不是直接抛出让进程崩溃。
七、关闭连接与连接池
单次脚本任务完成后,应关闭连接释放资源:
connection.end((err) => {
if (err) {
console.error('Error closing connection:', err.message);
return;
}
console.log('MySQL Connection Closed.');
});
但如果应用需要频繁处理数据库请求,每次新建连接开销较大。这时应使用连接池:
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'your_password',
database: 'companydb',
port: 3306,
connectionLimit: 10
});
pool.query('SELECT NOW() AS CurrentTime', (err, result) => {
if (err) throw err;
console.log(result);
});
连接池会复用已有连接,避免频繁握手。Web服务、API接口这类场景,连接池是更合理的选择。connection.end()对应的是单连接模式;连接池通常不需要手动关闭每个连接,进程退出时池会自行清理。
八、常用连接方法速查
| 方法 | 说明 |
|---|---|
mysql.createConnection() |
按配置创建连接对象 |
connection.connect() |
建立与MySQL的连接 |
connection.query() |
执行SQL查询 |
connection.end() |
关闭连接 |
mysql.createPool() |
创建连接池 |
pool.query() |
从池中取连接并执行查询 |
九、现在框架中的惯用写法
在Express、Koa、NestJS等框架中,通常不会在每个路由里直接写mysql.createConnection()。更常见的做法是:
-
把连接池封装为独立模块,例如
db.js,对外暴露query方法。 -
通过环境变量管理数据库配置,区分开发、测试、生产环境。
-
在应用启动时初始化连接池,在应用关闭时释放资源。
-
使用
mysql2/promise配合async/await,让异步代码更接近同步写法。
下面是一个mysql2Promise风格的简短示例,文件名可命名为code_02_pool.js:
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10
});
async function getCurrentTime() {
const [rows] = await pool.query('SELECT NOW() AS CurrentTime');
return rows[0].CurrentTime;
}
getCurrentTime()
.then((time) => console.log(time))
.catch((err) => console.error(err.message));
mysql2的pool.query()返回Promise,配合async/await可以减少回调嵌套。如果项目允许选择驱动,新项目直接使用mysql2是更省心的做法。
十、本节课程知识要点
-
Node.js连接MySQL需要安装第三方驱动,
mysql与mysql2是常见选择。 -
连接参数中的密码应通过环境变量注入,不要硬编码。
-
使用
?占位符传参,避免SQL注入。 -
单连接用
connection.end()关闭;高频请求场景用连接池。 -
连接失败时优先检查用户权限、数据库是否存在、MySQL服务是否运行。
-
新项目可优先考虑
mysql2/promise,旧项目迁移成本较低。
十一、练习思考题
-
把
app.js中的连接配置改为从.env文件读取,使用dotenv包加载。 -
在
employees表中插入三条记录,分别查询薪资大于8000的记录和全部记录。 -
把单连接模式改为连接池模式,观察代码结构变化。
-
尝试用
mysql2/promise重写插入与查询逻辑,对比回调写法的差异。 -
模拟密码错误、数据库不存在、MySQL未启动三种情况,记录终端输出并说明排查思路。