必须显式处理依赖关系,因sql server默认no action、mysql/postgresql默认restrict,子表存在引用即回滚删除;解决需三步:查外键名、删旧约束、重建带on delete cascade的约束,或改用逻辑删除。

不能直接删主表数据——外键默认拦着你,必须显式处理依赖关系。
为什么 DELETE FROM parent 会报错 DELETE statement conflicted with the REFERENCE constraint
这不是语法错误,是 SQL Server 默认用 NO ACTION 行为:只要子表里还有一行引用主表记录,整个删除操作立刻回滚。MySQL 和 PostgreSQL 默认也是 RESTRICT(语义等价于 NO ACTION),不是数据库坏了,是约束在尽职。
- 错误不看“有没有数据”,只看“有没有可能冲突”——哪怕子表只有 1 行关联记录,也会被拦住
-
FOREIGN_KEY_CHECKS = 0是 MySQL 特有指令,SQL Server 不支持,别套用 - 手动先删子表再删主表,容易漏删、顺序错、事务没包住,导致中间状态残留
给已有表加 ON DELETE CASCADE 的三步实操(SQL Server)
SQL Server 不允许 ALTER TABLE ... ADD FOREIGN KEY ... ON DELETE CASCADE 一步到位,必须拆成三步走:
- 查外键名:
SELECT name FROM sys.foreign_keys WHERE parent_object_id = OBJECT_ID('ChildTable') AND referenced_object_id = OBJECT_ID('ParentTable') - 删旧约束:
ALTER TABLE ChildTable DROP CONSTRAINT FK_ChildTable_ParentTable(把上步查到的名字填进去) - 重建带级联:
ALTER TABLE ChildTable ADD CONSTRAINT FK_ChildTable_ParentTable FOREIGN KEY (parent_id) REFERENCES ParentTable(id) ON DELETE CASCADE
注意:ON DELETE CASCADE 要求外键列本身可为空?不。它不要求 NULL,NOT NULL 列也能用;但 ON DELETE SET NULL 就必须定义该列为 NULL。
执行前必须逐层 SELECT COUNT(*) 估算影响范围
级联不是删一行带出一行,而是递归清整条依赖链——删一个 Users.Id = 123,可能触发 UserMessages → MessageAttachments → AttachmentLogs 连锁删除,且全程绕过应用层逻辑、不走触发器、不写审计日志。
- 先查:
SELECT COUNT(*) FROM UserMessages WHERE UserId = 123 - 再查:
SELECT COUNT(*) FROM MessageAttachments WHERE MessageId IN (SELECT Id FROM UserMessages WHERE UserId = 123) - 继续往下摸,直到最底层表;别跳层,也别靠猜
- 如果某层返回数千或上万行,就得重新评估:是不是该用软删除替代硬删?
比级联更稳的替代方案:软删除 + 查询过滤
当业务要求留痕(如财务、风控)、或依赖链太深不敢开级联时,is_deleted TINYINT(1) DEFAULT 0 是更可控的选择。
- 主表加字段后,所有
JOIN和WHERE必须显式加上AND is_deleted = 0,漏一处就查出“已删”脏数据 - 关联查询不能只改主表条件,子表也要加同样过滤,否则外键字段虽存在,业务语义已失效
- 物理清理可延后做,用定时任务分批执行,按依赖顺序
DELETE,压力可控、可中断、可监控
真正难的从来不是“怎么删”,而是“删完之后,业务逻辑是否还自洽”——级联快,但不可见;软删慢,但可追溯。选哪个,得看你的日志要不要留、审计能不能过、回滚敢不敢做。











