如果你还没有 MySQL 的基础知识,可以参考 MySQL 基础教程。
重要说明:旧版
mysql包已经不再维护,新项目强烈建议使用 mysql2。mysql2 完全兼容 mysql 的API,性能更好,原生支持 Promise / async‑await、预处理语句,支持 MySQL8.0。
安装驱动
npm install mysql2
不建议再使用 cnpm,直接使用官方 npm。
准备测试表 websites
执行下面SQL导入测试数据表,后续所有示例都操作这张表:
CREATE DATABASE IF NOT EXISTS test;
USE test;
DROP TABLE IF EXISTS websites;
CREATE TABLE websites (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
url VARCHAR(255),
alexa INT,
country VARCHAR(10)
);INSERT INTO websites(name,url,alexa,country) VALUES
('Google','https://www.google.com',1,'USA'),
('淘宝','https://www.taobao.com',13,'CN'),
('runoops教程','http://runoops.com',6988,'CN'),
('微博','http://weibo.com',22,'CN'),
('Facebook','https://www.facebook.com',3,'USA');
基础单次连接(仅适合脚本,不适合Web服务)
方式1:传统回调写法(兼容旧习惯)
const mysql = require('mysql2');// 创建连接
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '', // 修改为你的数据库密码
database: 'test',
charset: 'utf8mb4' // 推荐utf8mb4,支持emoji
}); connection.connect(function(err) {
if (err) {
console.error('连接失败:',err.stack);
return;
}
console.log('连接成功,threadId:',connection.threadId);
}); connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results[0].solution);
});connection.end();
运行:
node test.js
#输出 The solution is: 2
方式2:Promise / async‑await(现代开发推荐)
const mysql = require('mysql2/promise');async function run() {
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'test',
charset:'utf8mb4'
});
const [results] = await connection.query('SELECT 1+1 AS solution');
console.log('The solution is: ', results[0].solution);
await connection.end();
}run().catch(err=>console.error(err));
数据库连接参数完整说明
| 参数 | 描述 |
|---|---|
| host | 数据库主机地址,默认 localhost |
| user | 数据库用户名 |
| password | 数据库密码 |
| port | 端口,默认 3306 |
| database | 指定要使用的数据库 |
| charset | 连接字符集,生产务必使用 utf8mb4 |
| connectTimeout | 连接超时时间,单位毫秒 |
| timezone | 时区,默认 local |
| supportBigNumbers | 读取 bigint / decimal 大数时开启 |
| bigNumberStrings | bigint 以字符串返回,防止JS精度丢失 |
| multipleStatements | 是否允许一条query执行多条SQL,默认关闭(安全) |
| waitForConnections | 连接池:连接耗尽是否排队等待 |
| connectionLimit | 连接池最大连接数 |
⚠️ 不要开启
multipleStatements,存在SQL注入风险。
✅ 生产环境优先使用:连接池
Web服务不要用单次createConnection,频繁创建销毁连接性能差,连接池复用连接。
const mysql = require('mysql2/promise');// 创建连接池,全局复用
const pool = mysql.createPool({
host:'localhost',
user:'root',
password:'',
database:'test',
charset:'utf8mb4',
connectionLimit: 10,
waitForConnections: true,
queueLimit:0
}); // 测试连接池
async function testPool(){
const [rows] = await pool.query('SELECT 1+1 AS res');
console.log(rows[0].res);
}
testPool().catch(console.error);module.exports = pool;
连接池的连接会自动归还,一般不需要手动
.end();程序退出时才调用await pool.end()关闭全部连接。
数据库操作 CURD(操作 websites 表)
安全提醒:禁止直接拼接用户输入到SQL字符串,必须使用占位符
?传参,防止SQL注入。
查询数据 SELECT
async‑await版本(推荐)
const pool = require('./test');async function queryList(){
const sql = 'SELECT * FROM websites';
const [rows] = await pool.query(sql);
console.log('--------------------------SELECT----------------------------');
console.log(rows);
console.log('------------------------------------------------------------\n\n');
}queryList().catch(err=>console.error('[SELECT ERROR]',err.message));
回调版本(旧风格)
const mysql = require('mysql2');
const connection = mysql.createConnection({host:'localhost',user:'root',password:'',database:'test'});connection.connect();
const sql = 'SELECT * FROM websites';
connection.query(sql,function(err,result){
if(err){
console.log('[SELECT ERROR] - ',err.message);
return;
}
console.log('--------------------------SELECT----------------------------');
console.log(result);
console.log('------------------------------------------------------------\n\n');
});
connection.end();
插入数据 INSERT
async‑await版本:
const pool = require('./test');async function insertData(){
const addSql = 'INSERT INTO websites(name,url,alexa,country) VALUES(?,?,?,?)';
const params = ['Web工具','http://t.runoops.com',53453,'CN'];
const [result] = await pool.query(addSql,params);
console.log('--------------------------INSERT----------------------------');
console.log('INSERT ID:',result.insertId);
console.log('affectedRows:',result.affectedRows);
console.log('-----------------------------------------------------------------\n\n');
}insertData().catch(err=>console.error('[INSERT ERROR]',err.message));
执行成功后,查看数据表,即可以看到添加的数据:

更新数据 UPDATE
const pool = require('./test');async function updateData(){
const modSql = 'UPDATE websites SET name = ?,url = ? WHERE id = ?';
const params = ['参考手册','[http://ref.runoops.com/',6](http://ref.runoops.com/',6)];
const [result] = await pool.query(modSql,params);
console.log('--------------------------UPDATE----------------------------');
console.log('UPDATE affectedRows',result.affectedRows);
console.log('-----------------------------------------------------------------\n\n');
}
updateData().catch(err=>console.error('[UPDATE ERROR]',err.message));
执行成功后,查看数据表,即可以看到更新的数据:

删除数据 DELETE
const pool = require('./test');async function delData(){
const delSql = 'DELETE FROM websites where id=?';
const params = [6];
const [result] = await pool.query(delSql,params);
console.log('--------------------------DELETE----------------------------');
console.log('DELETE affectedRows',result.affectedRows);
console.log('-----------------------------------------------------------------\n\n');
}
delData().catch(err=>console.error('[DELETE ERROR]',err.message));
执行成功后,查看数据表,即可以看到 id=6 的数据已被删除:

事务示例(InnoDB引擎)
转账、订单等场景,多条SQL必须同时成功或者同时失败:
const pool = require('./test');async function runTransaction(){
const conn = await pool.getConnection();
try{
await conn.beginTransaction();
// 执行多条业务SQL
// await conn.query('UPDATE xxx SET ... WHERE id=?',[...])
await conn.commit();
console.log('事务提交成功');
}catch(err){
await conn.rollback();
console.error('事务回滚',err);
}finally{
conn.release(); // 归还连接到池,必须写
}
}
runTransaction();
错误处理与调试
- 所有数据库操作务必包裹
try/catch,避免异常直接崩溃整个服务; - 报错对象包含:
err.code错误码、err.sqlState、err.sql(执行的SQL语句,开发环境打印用于调试); - 开发环境打印完整错误,生产环境不要把原始错误返回前端,只打日志;
- 常见错误:
ER_ACCESS_DENIED_ERROR:账号密码错误,或远程访问权限未授权;ER_NO_SUCH_TABLE:表不存在;ER_DUP_ENTRY:唯一索引重复;ETIMEDOUT:连接超时,检查防火墙、MySQL服务、连接池参数。
示例统一捕获:
async function safeQuery(){
try {
const [rows] = await pool.query('SELECT * FROM websites');
return rows;
}catch(err){
console.error('MySQL异常',{code:err.code,msg:err.message,sql:err.sql});
throw new Error('数据库操作失败,请稍后重试');
}
}
性能与安全优化建议
- Web项目必须使用连接池,禁止每次请求新建/关闭连接;
- SQL永远使用占位符
?,不要字符串拼接SQL,防止注入; - 避免
SELECT *,只查询业务需要的字段; - 查询大数据必须加
LIMIT,禁止全表扫描; - where条件、排序字段建立索引;避免
%xxx前置通配符,会索引失效; - 批量数据优先批量插入,不要循环单条插入;
- 数据库密码不要硬编码写代码,放到环境变量
process.env; - 事务内代码尽量精简,缩短事务时间,减少锁冲突、死锁;
- 字符集统一
utf8mb4,支持中文和emoji表情; - 开启MySQL慢查询日志,监控慢SQL。
mysql2 与旧mysql包对比
| 包 | Promise | 维护状态 | 推荐度 |
|---|---|---|---|
| mysql | 需要额外封装 | 停止维护 | ❌老项目兼容 |
| mysql2 | 原生支持async/await | 持续更新 | ✅新项目首选 |