逻辑删除是UPDATE状态列而非DELETE,需统一用CHAR(1) DEFAULT 'N' NOT NULL规范字段,业务SQL必须显式过滤is_deleted = 'N',并禁用直接DELETE以防止误删。
Oracle逻辑删除不是DELETE,而是UPDATE状态列
oracle里没有原生“逻辑删除”语法,所谓逻辑删除,就是用update把某列(比如is_deleted)设为标记值(如'y'或1),让业务查询时自动过滤掉。这和delete语句有本质区别:delete会进回滚段、触发before/after delete触发器、可能被闪回;而逻辑删除只是普通dml,走的是update路径。
常见错误是把逻辑删除写成DELETE FROM t WHERE id = ?,结果数据真没了——这不是逻辑删除,是物理误删。
- 必须明确区分:逻辑删除 =
UPDATE ... SET is_deleted = 'Y' - 所有业务SQL的
WHERE子句都要加AND is_deleted = 'N'(或对应有效值),不能只靠应用层过滤 - 索引要覆盖
(is_deleted, ...)组合,否则全表扫描风险极高
触发器不能自动补逻辑删除条件,但可拦截非法DELETE
Oracle触发器无法让DELETE FROM t WHERE id = 1悄悄变成UPDATE t SET is_deleted = 'Y' WHERE id = 1——那是重写SQL,触发器做不到。但可以用INSTEAD OF触发器在视图上做转换,或用BEFORE DELETE触发器强行阻止并抛错。
更实用的做法是:在关键表上建BEFORE DELETE触发器,一检测到DELETE就RAISE_APPLICATION_ERROR,强制所有删除走存储过程接口。
-
BEFORE DELETE ON users触发器里写IF TRUE THEN RAISE_APPLICATION_ERROR(-20002, 'Direct DELETE prohibited. Use soft_delete_by_id instead.'); END IF; - 这样能堵住ORM自动生成
DELETE、开发手写SQL误删等漏洞 - 注意:触发器对
TRUNCATE无效,TRUNCATE会绕过所有DML触发器
带状态位的触发器要防递归和时序陷阱
如果想在UPDATE某字段时自动同步更新is_deleted或updated_at,得小心触发器递归和执行顺序。例如:给status字段加BEFORE UPDATE触发器,设is_deleted = 'Y',但这个UPDATE又会再次触发自己,死循环。
- 用
PRAGMA AUTONOMOUS_TRANSACTION是错的——它开新事务,提交后无法回滚主事务 - 正确做法是用
IF UPDATING('status')判断,并加AND :NEW.is_deleted IS NULL等前置条件避免重复设置 - 时间戳类字段(如
updated_at)建议用DEFAULT ON NULL SYSTIMESTAMP,比触发器更轻量、无递归风险 - 如果必须用触发器更新状态位,优先选
BEFORE ROW,确保在约束检查前完成赋值
软删除字段类型和值必须全局统一
同一个库中,is_deleted字段在不同表里用VARCHAR2(1)、CHAR(1)、NUMBER(1)甚至BOOLEAN(PL/SQL里可用,表里不行),会导致联合查询、视图、存储过程处处出问题。
比如WHERE is_deleted = 'Y'在VARCHAR2表里正常,在NUMBER表里就永远不匹配——Oracle隐式转换规则复杂,别赌。
- 建表规范强制:软删除字段统一用
CHAR(1) DEFAULT 'N' NOT NULL,有效值仅'Y'/'N' - 所有存储过程参数(如
p_deleted_flag IN VARCHAR2)传入前先UPPER(TRIM(p_deleted_flag)),防空格和大小写 - 查询时永远用
is_deleted = 'Y',不用is_deleted = TRUE或is_deleted = 1
最易被忽略的是:应用代码里写了WHERE is_deleted = 0,而数据库字段是CHAR(1),Oracle会把0转成字符串'0'去比,结果永远查不到——这种错线上很难发现,因为测试数据少时碰巧值对了。











