Row-level security performance in PostgreSQL, measured
a day ago
- 简单的行级安全(RLS)策略使用直接列比较(例如 tenant_id = setting)时,如果查询已经按该列过滤,则不会产生可测量的性能开销。
- RLS 的性能开销源于三个方面:租户 ID 的获取方式、成员资格子查询,以及查询中非防泄漏(non-leakproof)函数的使用。
- 声明为 VOLATILE 的 PL/pgSQL 函数会导致逐行执行,从而产生顺序扫描(最多约 1.9 秒)。解决方法是将函数声明为 STABLE,或将调用包装在 (SELECT ...) 子查询中。
- SQL 函数默认会被内联,但添加 SECURITY DEFINER 或 SET search_path 会破坏内联。将它们声明为 STABLE 可以避免这种依赖。
- 函数默认是 PARALLEL UNSAFE,这会禁用并行计划。将函数声明为 STABLE PARALLEL SAFE 可以恢复并行性,但在连接池环境中可能会导致计划缓存问题。
- 成员资格子查询(IN (SELECT ...))会逐行求值,每次查询增加约 80 毫秒的开销。使用 = ANY (ARRAY(SELECT ...)) 可以改善计数,但可能会使依赖索引顺序的查询变差。
- 多租户的最佳实践:在查询前一次性解析成员资格,并通过设置传递租户 ID,避免在策略中使用子查询。
- 非防泄漏函数(例如 lower()、LIKE)会阻止 RLS 下表达式使用索引。索引仅用于租户列,然后过滤器会应用于该租户的所有行,从而对大租户造成不成比例的拖慢。
- 针对防泄漏问题的修复方法包括:为 lower() 使用存储生成列,使用防泄漏运算符(text_pattern_ops 的 ~>=~、~<~),或将函数标记为 leakproof(不推荐)。
- 要诊断 RLS 性能问题:使用提供的查询检查策略中是否存在 VOLATILE 函数,并以应用程序角色运行 EXPLAIN,查看条件是否变为 Filter 而不是 Index Cond。