按另一张表的条件删除
删除条件来自另一张表的场景很常见:把已退款订单删掉、把无引用的配置清理掉。两种数据库的语法结构不同,最要命的差别在 MySQL——多表删除语句里,真正被删的是别名清单里列出的那张表,写错别名会得到「语句成功执行但删了零行」或者删错表的结果,而且都不会报错。执行前用同样的条件先跑一次查询统计行数,是这里唯一可靠的防线。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL 12 及以上 |
|---|---|
| MySQL 写法 | 删除关键字后列出要删的别名,再写连接条件 |
| PostgreSQL 写法 | 目标表后跟辅助表清单,再写条件 |
| 别名规则 | MySQL 决定删哪张表,PostgreSQL 只写辅助表 |
| 执行前置动作 | 用同样条件先查一次,核对待删行数 |
典型使用场景
- 按关联表状态删除主表数据,例如删掉已全额退款的订单
- 清理无引用记录:删除没有任何明细的主单
- 按用户维度清理:删除已注销用户的业务数据
- 归档收尾:确认数据已复制后删除源表记录
- 级联清理前的预演:先统计影响范围再执行
两库写法与预演
两库写法与预演
-- 先预演:用完全相同的条件统计待删行数
SELECT COUNT(*)
FROM orders o
JOIN refunds r ON r.order_id = o.id
WHERE r.status = 'approved';
-- MySQL:删除关键字后列出要删的别名
DELETE o -- 必须写对目标表别名
FROM orders o
JOIN refunds r ON r.order_id = o.id
WHERE r.status = 'approved';
-- PostgreSQL:目标表后跟辅助表清单
DELETE FROM orders o
USING refunds r
WHERE r.order_id = o.id
AND r.status = 'approved';
-- 包在事务里执行,确认行数符合预期后再提交
-- BEGIN; ... ; COMMIT;结构对照
| 对比项 | MySQL | PostgreSQL |
|---|---|---|
| 要删的表 | 写在删除关键字之后 | 写在删除关键字之后,只写一次 |
| 辅助表 | 写在 FROM 里 | 写在 USING 列表里 |
| 别名写法 | 必须与 FROM 中的别名一致 | 别名写在目标表之后 |
| 写错的后果 | 可能删零行或删错表,均不报错 | 列不存在时直接报错 |
| 多表删除 | 可一次删多张表 | 一次只删一张表 |
MySQL 的多表删除允许一次删多张表,这在级联清理时很方便,但风险也随之放大。建议始终只删一张表,其余表作为连接条件使用。
关联条件不唯一时,两库的删除行为都是「命中即删」,不会因为一对多而多次删除同一行,但预演统计的行数会包含重复计数带来的膨胀,核对时要用去重后的主键数比较。