CREATE POLICY 时必须显式指定 FOR DELETE,PostgreSQL 不会为未声明的操作自动应用策略;即使已定义 SELECT 或 UPDATE 策略,DELETE 仍默认允许(仅受表级权限约束),需单独创建 FOR DELETE 策略并用 USING 表达式限定可删行,不支持 WITH CHECK,且强制行级安全(FORCE ROW LEVEL SECURITY)才使所有者受限制。

CREATE POLICY 时必须显式指定 FOR DELETE
PostgreSQL 不会自动为未声明的操作类型应用策略。即使你已经为 SELECT 或 UPDATE 创建了策略,DELETE 仍默认允许(只要用户有表级 DELETE 权限)。要限制 DELETE,必须单独创建一条 FOR DELETE 策略。
常见错误现象:用户能正常 SELECT 自己的数据,也能成功执行 DELETE FROM table WHERE id = 123,但实际删掉了别人的数据——因为根本没定义 DELETE 策略。
实操建议:
- 策略必须用
FOR DELETE明确声明,不能省略或混在FOR ALL里依赖隐式覆盖 -
USING表达式决定“哪些行允许被删除”,它只检查现有行是否匹配条件,不验证新值(DELETE没有新值) - 例如限制用户只能删自己插入的记录:
CREATE POLICY delete_own_data ON sensitive_data FOR DELETE USING (user_id = current_user); - 若需更严格控制(如禁止删除已归档数据),可在
USING中加入复合条件:USING (user_id = current_user AND status != 'archived')
DELETE 策略不支持 WITH CHECK
WITH CHECK 只对涉及“写入新状态”的操作有效,比如 INSERT 和 UPDATE。而 DELETE 是移除整行,没有“新行值”可校验,因此语法上不允许 WITH CHECK 子句。
这意味着你无法用 RLS 阻止“因关联更新导致的间接删除”,比如触发器或 ON DELETE CASCADE。RLS 的 DELETE 限制仅作用于直接 DELETE 语句。
实操建议:
- 不要尝试写
CREATE POLICY ... FOR DELETE WITH CHECK (...),会报错syntax error at or near "WITH" - 如果业务要求“软删除”或状态驱动删除,应把逻辑放在
USING条件中,例如:USING (user_id = current_user AND deleted_at IS NULL) - 级联删除不受 RLS 约束,需靠外键约束或应用层控制
FORCE ROW LEVEL SECURITY 影响 DELETE 行为
默认情况下,表所有者(owner)和超级用户(superuser)执行 DELETE 时会绕过 RLS 策略。如果你希望连所有者也受限制,必须启用强制模式:
ALTER TABLE sensitive_data FORCE ROW LEVEL SECURITY;
这个命令影响所有操作类型,包括 DELETE。一旦启用,所有角色(含所有者)都必须满足策略条件才能删除行。
容易踩的坑:
- 误以为
ENABLE ROW LEVEL SECURITY就够了——其实它只启用策略机制,不改变所有者豁免规则 - 在生产环境执行
FORCE前未测试运维账号行为,导致 DBA 无法紧急清理脏数据 -
FORCE是表级开关,不能按用户或会话动态关闭;调试时需临时DISABLE再恢复
测试 DELETE 策略是否生效的关键步骤
光看 CREATE POLICY 成功不等于策略起作用。必须用目标用户身份连接并实测,否则容易漏掉权限或上下文问题。
实操建议:
- 用
psql -U target_user -d dbname切换到被限制用户,再执行DELETE,观察是否报错no rows to delete或静默失败(取决于策略条件是否匹配) - 确认该用户确实有表级
DELETE权限:GRANT DELETE ON sensitive_data TO target_user;,否则 RLS 根本不会触发(先卡在权限层) - 检查策略是否被禁用:
SELECT polname, polcmd, polqual, polwithcheck FROM pg_policy WHERE polrelid = 'sensitive_data'::regclass;,确认polcmd = 'D'且polqual非空 - 注意
current_user和session_user差异:若使用连接池或代理,实际会话用户可能不是预期角色,建议统一用current_user并确保登录角色与策略逻辑一致
DELETE 控制本质是“过滤可操作的行”,不是“拦截语句”。哪怕策略条件写得再严,只要有一行匹配,DELETE 就会执行成功——所以策略表达式的边界 case(比如 NULL 值、大小写、时区)必须和业务数据严格对齐。

















