oracle无内置软删除语法,需通过更新状态列(如is_deleted)实现;硬删不可逆且违反合规要求,软删本质是标记无效并配合查询过滤。
oracle 中没有内置的“软删除”语法,必须靠显式更新状态列(如 is_deleted、status、del_flag)来模拟。直接 delete 是硬删,不可逆;软删本质是 update + 约束/视图/应用层过滤配合。
为什么不能用 DELETE 语句替代软删除
硬删后数据彻底丢失,无法回溯、审计或恢复;触发器、外键级联、物化视图刷新等都可能因行消失而异常;且违反多数企业级系统对操作留痕的合规要求。软删除的核心不是“不删”,而是“标记为无效”,所有后续查询必须主动排除 is_deleted = 'Y' 的记录。
- 应用层漏加
WHERE is_deleted = 'N'就会查出已“删”数据 - 索引若未包含
is_deleted列,全表扫描风险陡增 - 历史报表、快照、ETL 任务若未适配该字段,结果将失真
带事务控制的软删除存储过程模板
以下是一个安全、可复用的软删除过程,支持按主键批量更新,并自动处理异常与提交边界:
CREATE OR REPLACE PROCEDURE soft_delete_by_id(
p_table_name IN VARCHAR2,
p_pk_column IN VARCHAR2,
p_pk_values IN SYS.ODCIVARCHAR2LIST, -- 传入 ID 列表,如 SYS.ODCIVARCHAR2LIST('1001','1002')
p_status_col IN VARCHAR2 DEFAULT 'is_deleted',
p_deleted_flag IN VARCHAR2 DEFAULT 'Y'
) AS
l_sql VARCHAR2(2000);
BEGIN
-- 校验表和列是否存在(防止 SQL 注入)
IF NOT EXISTS (SELECT 1 FROM user_tables WHERE table_name = UPPER(p_table_name)) THEN
RAISE_APPLICATION_ERROR(-20001, 'Table ' || p_table_name || ' does not exist');
END IF;
<pre class="brush:php;toolbar:false;">l_sql := 'UPDATE ' || p_table_name ||
' SET ' || p_status_col || ' = :flag ' ||
' WHERE ' || p_pk_column || ' IN (SELECT column_value FROM TABLE(:ids))';
EXECUTE IMMEDIATE l_sql USING p_deleted_flag, p_pk_values;
DBMS_OUTPUT.PUT_LINE('Soft-deleted ' || SQL%ROWCOUNT || ' rows.');EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE; END; /
-
p_pk_values必须用SYS.ODCIVARCHAR2LIST或SYS.ODCINUMBERLIST,不能拼字符串——否则遇单引号或 SQL 注入直接崩 - 不建议在过程内写
COMMIT:调用方应控制事务边界,否则嵌套调用时易出现部分提交 - 若表有触发器(如日志记录),确保其逻辑兼容
UPDATE场景,而非只监听DELETE
如何让老代码“无感”迁移到软删除
直接改所有 DELETE FROM t WHERE ... 为调用存储过程成本高、易遗漏。更稳妥的做法是:
- 创建同名
VIEW替代原表,WHERE is_deleted = 'N'自动过滤,应用层无需改 SQL - 重命名原表为
t_base,新建VIEW t AS SELECT * FROM t_base WHERE is_deleted = ''N'' - 对 DML 操作建
INSTEAD OF触发器,把DELETE转为UPDATE t_base SET is_deleted = ''Y'' - 注意:
INSTEAD OF触发器不适用于远程表、对象表或含LONG列的表
容易被忽略的性能与维护陷阱
软删除跑久了,表里“死数据”越来越多,但索引仍为其维护结构,导致写放大、执行计划劣化、备份体积膨胀。
- 定期归档已软删超 90 天的数据:用
INSERT INTO archive_t SELECT * FROM t_base WHERE is_deleted = ''Y'' AND del_time ,再 <code>DELETE - 关键查询字段 +
is_deleted建组合索引,例如CREATE INDEX idx_user_del_stat ON users (status, is_deleted) -
is_deleted列务必设默认值(DEFAULT 'N')并加NOT NULL约束,避免NULL导致WHERE is_deleted = 'N'漏匹配
软删除不是加一列就完事,它牵扯到查询路径、索引策略、归档机制和全链路一致性。最危险的时刻,往往是上线后没人再看那列值是否真被更新了。











