DELETE语句必须带WHERE条件,否则会清空整张表;应先用SELECT验证、用预处理防注入、用事务保证一致性、优先软删除而非硬删除。

DELETE 语句必须带 WHERE 条件
不加 WHERE 的 DELETE FROM users; 会清空整张表,不是删错一条,是删光所有数据。初学者常在测试环境随手写 DELETE FROM log; 就回车,结果发现日志全没了。
实操建议:
- 写完
DELETE语句,先改成SELECT *跑一遍,确认匹配的行数和内容是否符合预期 - 开发阶段强制用
WHERE id = ?或WHERE status = 'draft'这类明确、可预判的条件,避免用WHERE name LIKE '%abc%'这类易误伤的模糊条件 - 上线前检查 SQL 是否被拼接了空字符串或未过滤的用户输入,比如
"DELETE FROM users WHERE id = " . $_GET['id']—— 这种写法一旦$_GET['id']是1 OR 1=1,就变全表删除
用 PDO 预处理,别拼接 SQL 字符串
手动拼接 $_POST 或 $_GET 到 SQL 里,等于给 SQL 注入大开绿灯。哪怕只是删自己账号,攻击者也能通过修改请求参数删掉管理员记录。
正确做法是用 PDO 预处理绑定参数:
立即学习“PHP免费学习笔记(深入)”;
try {
$pdo = new PDO($dsn, $user, $pass);
$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id AND status = 'inactive'");
$stmt->execute(['id' => (int)$_POST['user_id']]);
} catch (PDOException $e) {
error_log('Delete failed: ' . $e->getMessage());
}
注意点:
-
:id占位符比?更清晰,尤其多参数时;但无论哪种,都必须调用execute()传值,不能直接把变量塞进 SQL 字符串 -
(int)强转能防基础注入,但不能替代预处理——比如删文章时按标题删,title是字符串,就必须用:title绑定,不能靠强转 - 执行后检查
$stmt->rowCount()返回值,如果是 0,说明没删到数据,可能是 ID 不存在或条件不匹配,别默认“删成功了”
事务不是可选项,是安全底线
删数据常伴随关联操作:比如删用户,要同步删其订单、评论、头像文件。中间任何一步失败(如磁盘满导致文件删不了),数据库却已提交了用户记录,就会出现数据不一致。
必须用事务兜底:
$pdo->beginTransaction();
try {
$pdo->exec("DELETE FROM users WHERE id = 123");
$pdo->exec("DELETE FROM orders WHERE user_id = 123");
unlink('/uploads/avatar_123.jpg');
$pdo->commit();
} catch (Exception $e) {
$pdo->rollback();
throw $e;
}
关键提醒:
- MySQL 默认存储引擎 MyISAM 不支持事务,删数据前先确认表是
InnoDB(用SHOW CREATE TABLE users;查) -
unlink()这类 PHP 文件操作无法回滚,得放在事务 之后,或改用软删除(把status改为deleted)来规避 - 事务里别做耗时操作(如发邮件、调外部 API),否则锁表时间过长,影响并发
软删除比硬删除更适合多数业务场景
真实业务中,“删”往往只是“用户不想再看到”,而不是“数据彻底无价值”。硬删除后审计、恢复、统计都成问题,还容易因外键约束失败报错。
推荐用软删除字段:
- 加一个
deleted_at字段(DATETIME NULL),删时只更新它:UPDATE users SET deleted_at = NOW() WHERE id = ? - 所有查询加默认条件
WHERE deleted_at IS NULL,可用视图或 ORM 范围作用域统一处理 - 真正需要清理时,再用定时任务跑
DELETE FROM users WHERE deleted_at ,并确保有备份
注意:软删除会让索引效率略降,高频读写的表需评估;另外,COUNT(*) 和分页逻辑要重写,不然会把已删数据也算进去。











