内容提要
基于 PostgreSQL 17.11 实测,分析“new row violates row-level security policy”错误:该错误有四种形式、六类成因,包括租户不匹配、未设置租户、缺少插入策略、RETURNING 或 ON CONFLICT 需要 SELECT 策略、限制性策略拒绝、upsert 命中无权更新的行。排查时应检查当前租户设置、pg_policies 中对应命令与角色的策略,以及语句是否读取该行。
延伸解读
错误消息的四种形式指向不同原因
文章基于 PostgreSQL 17.11 实测指出,'new row violates row-level security policy' 有四种拼写:无策略名、带策略名、带 (USING expression) 无策略名、带策略名和 (USING expression)。无策略名通常表示新行未通过任何宽松策略;带策略名则说明宽松策略通过但被限制性策略拒绝,且该策略不一定是 INSERT 策略,也可能是 SELECT 策略。带 (USING expression) 无策略名常见于 upsert 命中无权更新的现
RETURNING 和 ON CONFLICT 会额外触发 SELECT 策略检
文章强调,RETURNING 子句以及带冲突目标的 ON CONFLICT 会使语句读取新行,从而必须通过 SELECT 策略。在仅配置 INSERT 策略的测试中,普通 INSERT 成功,但加上 RETURNING 或 ON CONFLICT 后立即失败。这意味着 ORM 自动添加 RETURNING 以获取生成键时,可能在应用层报错,而 psql 中同样的插入却正常。排查时需确认是否为该语句配置了对应的 SELECT 策略。
upsert 与 MERGE 在策略检查上行为不同
文章实测发现,upsert(ON CONFLICT DO UPDATE)在插入前就会检查 INSERT 策略的 WITH CHECK,即使最终只执行更新或什么都不做。而 MERGE 没有独立策略,它根据实际执行的动作应用 SELECT、INSERT、UPDATE、DELETE 策略。因此,当角色有更新权限但无插入权限时,MERGE 可能成功更新现有行,而 upsert 会失败。但若 MERGE 的源数据包含新行并触发插入动作,整个语句仍会因缺少 INSERT 策略而失败。
排查时应检查租户设置、策略和语句读取行为
文章建议在失败写入的同一事务中、以应用角色执行三项检查:确认 current_setting('app.tenant', true) 是否返回预期租户;查询 pg_policies 确认是否存在适用于该命令和角色的策略;判断语句是否通过 RETURNING 或 ON CONFLICT 读取行,从而需要 SELECT 策略。若租户未设置或为空,比较结果为 NULL,策略会拒绝。连接池环境下尤其要检查租户设置是否在正确的事务中生效。
Q&A
PostgreSQL 报错 new row violates row-level security policy 是什么意思?
该错误表示 PostgreSQL 拒绝了一行即将写入的数据,因为该行未通过行级安全策略检查:要么没有针对当前角色的宽松策略允许通过,要么被限制性策略拒绝。错误码为 SQLSTATE 42501,与权限不足相同。
哪些原因会导致 new row violates row-level security policy 错误?
根据实测,有六类原因:1) 行的租户与连接设置的租户不匹配;2) 完全没有设置租户;3) 该角色没有插入策略;4) 语句需要 SELECT 策略(如 RETURNING 或 ON CONFLICT);5) 限制性策略拒绝;6) upsert 命中一个该角色无权更新的已存在行。
为什么 upsert 在行已存在时也会触发 new row violates row-level security policy?
因为插入策略的 WITH CHECK 表达式会在 PostgreSQL 判断是否存在冲突之前对所有提议插入的行进行检查。即使该行最终只会执行更新,插入策略仍然会被检查,所以如果角色没有插入策略,upsert 就会失败。
为什么普通的 INSERT 能成功,但加上 RETURNING 或 ON CONFLICT 后就报 new row violates row-level security policy?
因为 RETURNING 和带冲突目标的 ON CONFLICT 会使语句读取它写入的行,而读取操作需要通过 SELECT 策略。如果角色没有针对新行的 SELECT 策略,这些语句就会失败。不带冲突目标的 ON CONFLICT DO NOTHING 不会触发此问题。
如何排查 new row violates row-level security policy 错误?
在失败写入的同一事务中,以应用连接的角色执行三项检查:1) 检查 current_setting('app.tenant', true) 是否返回预期的租户;2) 查询 pg_policies 确认是否存在针对该命令和角色的策略;3) 判断语句是否读取该行(如 RETURNING 或 ON CONFLICT),若是则还需要 SELECT 策略。
哪些操作不会触发 new row violates row-level security policy 错误?
实测中以下情况不会触发:COPY FROM 会直接报 COPY FROM not supported with row-level security;缺少 GRANT 会报 permission denied;表所有者未启用 FORCE ROW LEVEL SECURITY 时插入不会报错;被策略过滤的 UPDATE/DELETE 返回 0 行;MERGE 匹配到无权更新的行会报 target row violates row-level security policy;BEFORE INSERT 触发器可能改变租户值从而影响结果。