为什么删除操作值得单独拿出来讲
增删改查四个动作里,删除往往是最容易被轻视的一个。很多开发者觉得DELETE不就是DELETEFROM表WHERE条件吗,有什么好说的。实际项目里出问题的恰恰是删除:WHERE条件写漏导致全表清空、表名拼错导致接口报错、用户输入直接拼接SQL导致注入、删完之后不判断affectedRows导致前端提示“删除成功”但数据根本没动。
这篇文章围绕Node.js+mysql2操作MySQL的DELETE语句展开,把删除这件事讲透。
先分清DELETE和DROPTABLE
这两个语句经常被混淆,但做的事情不同。
DELETE是DML(数据操作语言),它删除的是表中的行记录,表结构、列定义、索引、约束都保留。删完之后表还在,只是数据少了。
DROPTABLE是DDL(数据定义语言),它直接把整张表连结构带数据一起抹掉,表本身不复存在。
-- 只删 id 为 1 的那条员工记录,表还在
DELETE FROM employees WHERE id = 1;
-- 整张 employees 表消失,结构和数据都没了
DROP TABLE employees;
项目里做业务删除时用的是DELETE,DROPTABLE一般只在迁移脚本或清理废弃表时出现。把这两个搞混,后果不是删错一行数据,而是整张表没了。
DELETE的基本语法与WHERE的重要性
DELETE FROM table_name WHERE condition;
table_name指定从哪张表删,WHERE指定删哪些行。
这里有一条必须记住的规则:WHERE子句决定了删除的范围,省略WHERE就是删除全表所有记录。MySQL不会因为你没写WHERE就拒绝执行,它会老老实实把整张表清空。所以生产环境执行DELETE之前,先把这个条件对应的SELECT跑一遍,看看选出来的行数对不对,再换成DELETE。
环境准备
动手之前确认几件事:
-
系统已安装Node.js
-
MySQLServer已安装并处于运行状态
-
项目里已安装mysql2包
-
目标数据库和employees表已存在
安装mysql2:
npm install mysql2
本文示例统一使用下面这张employees表:
| id | name | department | salary | |
|---|---|---|---|---|
| 1 | JohnSmith | john.smith@example.com | IT | 65000 |
| 2 | EmmaJohnson | emma.johnson@example.com | HR | 55000 |
| 3 | MichaelBrown | michael.brown@example.com | Finance | 70000 |
| 4 | SophiaDavis | sophia.davis@example.com | Marketing | 60000 |
删除单条记录
按主键删除是最常见也安全的场景,因为id唯一,最多只会命中一行。
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'your_password',
database: 'companydb'
});
const sql = "DELETE FROM employees WHERE id = 2";
connection.query(sql, (err, result) => {
if (err) {
console.error(err);
return;
}
console.log("员工记录已删除");
});
connection.end();
connection.query()把SQL发给MySQL执行,回调里的err为null就说明语句本身没报错。注意这里只代表“语句执行成功”,不代表“确实删掉了一行”,后面会讲怎么判断。
删除多条记录
WHERE条件不局限于主键,任何能匹配多行的条件都可以。
const sql = "DELETE FROM employees WHERE department = 'Marketing'";
connection.query(sql, (err, result) => {
if (err) {
console.error(err);
return;
}
console.log("市场部员工记录已删除");
});
只要department等于Marketing的行都会被删掉。这类批量删除在执行前一定要先用SELECT确认影响范围,尤其是条件字段没有索引或者取值比较宽泛的时候。
用参数化查询处理用户输入
把用户传来的值直接拼进SQL字符串是典型的安全隐患。假设前端传过来一个id,你写成:
const sql = `DELETE FROM employees WHERE id = ${employeeId}`;
如果employeeId被构造成1OR1=1,整张表就被清空了。正确做法是用?占位符,把值作为数组单独传给query方法:
const employeeId = 3;
const sql = "DELETE FROM employees WHERE id = ?";
connection.query(sql, [employeeId], (err, result) => {
if (err) {
console.error(err);
return;
}
console.log("员工记录已删除");
});
mysql2会自动把占位符替换成经过转义的值,字符串会加引号,数字按数字处理,注入的构造串会失去作用。这是mysql2相比自己拼字符串最实际的价值。
多个占位符就按顺序传多个值:
const sql = "DELETE FROM employees WHERE department = ? AND salary < ?";
connection.query(sql, ['IT', 60000], callback);
用affectedRows判断实际删了几行
result.affectedRows表示这条DELETE真正影响的行数。这是判断删除结果最直接的依据。
const sql = "DELETE FROM employees WHERE department = 'HR'";
connection.query(sql, (err, result) => {
if (err) {
console.error(err);
return;
}
console.log("受影响行数:", result.affectedRows);
});
affectedRows为1说明删掉了一行,为0说明没有任何行匹配条件。很多接口的bug就出在这里:只判断err是否为null,然后返回“删除成功”,但用户删的id根本不存在,affectedRows是0,数据一条没动。前端拿到“成功”提示,刷新一看记录还在。
删完之后验证一下
删除是写操作,想确认结果是否生效,用SELECT查一次最直观。
const sql = "SELECT * FROM employees WHERE id = 2";
connection.query(sql, (err, results) => {
if (err) {
console.error(err);
return;
}
console.log(results);
});
如果输出是空数组[],说明id为2的记录已经不在了。这个小动作在调试删除逻辑时很有用,比反复看日志快。
清空整张表但不删表结构
不写WHERE就是删除所有行:
const sql = "DELETE FROM employees";
connection.query(sql, (err, result) => {
if (err) {
console.error(err);
return;
}
console.log("全部员工记录已删除");
console.log("受影响行数:", result.affectedRows);
});
执行后表结构、列、索引、约束都保留,只是数据清空。如果确实需要清空全表,MySQL里还有TRUNCATETABLE,它比不带WHERE的DELETE更快,因为不走逐行删除和事务日志,但TRUNCATE不能带条件,也不能回滚,用之前要清楚区别。
错误处理:表名写错这类问题怎么暴露
const sql = "DELETE FROM employee WHERE id = 1";
connection.query(sql, (err, result) => {
if (err) {
console.error("错误:", err.message);
return;
}
console.log("员工记录已删除");
});
实际表名是employees,这里写成了employee,MySQL会返回:
Error: Table 'companydb.employee' doesn't exist
err.message把原因说得很清楚。数据库连接失败、权限不足、外键约束阻止删除,也都会通过err传回来。回调风格里必须显式判断err并return,否则后续代码会拿着undefined的result继续跑。
完整示例:连接、删除、判断结果、关闭连接
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'your_password',
database: 'companydb'
});
const employeeId = 4;
const sql = "DELETE FROM employees WHERE id = ?";
connection.query(sql, [employeeId], (err, result) => {
if (err) {
console.error("错误:", err.message);
return;
}
if (result.affectedRows > 0) {
console.log("员工记录已删除");
} else {
console.log("未找到对应员工");
}
console.log("受影响行数:", result.affectedRows);
});
connection.end();
这段代码把前面几个要点串起来了:mysql2引入、连接建立、参数化查询、affectedRows判断、连接关闭。affectedRows大于0才提示删除成功,等于0就明确告诉调用方没找到记录,避免误报。
现在写法:Promise与async/await
回调风格在稍大的项目里容易形成嵌套。mysql2提供了promise版本,配合async/await写起来更线性,错误处理也能用try/catch统一收口。
const mysql = require('mysql2/promise');
async function deleteEmployee(employeeId) {
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'your_password',
database: 'companydb'
});
try {
const sql = "DELETE FROM employees WHERE id = ?";
const [result] = await connection.execute(sql, [employeeId]);
if (result.affectedRows === 0) {
return { success: false, message: "未找到对应员工" };
}
return { success: true, affectedRows: result.affectedRows };
} catch (err) {
console.error("删除失败:", err.message);
throw err;
} finally {
await connection.end();
}
}
这里用的是connection.execute(),它和query都能执行参数化语句,execute走的是预处理语句路径,在需要反复执行同类语句时更合适。finally里关闭连接,保证异常情况下连接也能释放。
本节课程知识要点
-
DELETE删除的是行记录,DROPTABLE删除的是整张表,两者不可混用
-
不写WHERE会清空全表,执行前先用SELECT验证条件范围
-
用户输入一律走
?占位符,不要拼接字符串 -
用
result.affectedRows判断实际删除行数,不能只看err是否为null -
表名、字段名拼写错误会通过err.message暴露,回调里必须判断并return
-
大批量删除或线上操作前做好备份,删除不可逆
-
mysql2/promise配合async/await更适合现在项目结构
练习思考题
-
写一条DELETE语句,删除employees表中salary低于60000且department为IT的记录,并用affectedRows输出删了几行。
-
把上面的回调写法改写成mysql2/promise+async/await版本,要求删除失败时抛出异常,删除0行时返回明确提示。
-
如果一张表被另一张表通过外键引用,直接DELETE被引用的行会发生什么?如何在不破坏数据完整性的前提下处理?
-
不使用WHERE的DELETE和TRUNCATETABLE都能清空表,说说各自适用的场景和限制。