PostgreSQL RLS策略中函数过滤为何无法利用索引提升性能?
RLS策略中
USING (tenant_id = get_tenant_id())无法利用索引过滤的原因及解决方法 问题现象
仅依赖RLS策略实现租户数据隔离时,执行SELECT count(*) FROM people的性能极差:
SET app.tenant_id = 2; SET ROLE user; EXPLAIN ANALYZE SELECT count(*) FROM people; RESET ROLE; RESET app.tenant_id;
执行计划显示数据库先扫描全量索引数据,再通过Filter过滤非目标租户的数据:
Aggregate (cost=32570.08..32570.09 rows=1 width=8) (actual time=62.855..62.856 rows=1 loops=1) -> Index Only Scan using index_people_on_tenant_id on people (cost=0.29..32429.13 rows=56381 width=0) (actual time=58.639..62.642 rows=6000 loops=1) Filter: (tenant_id = get_tenant_id()) Rows Removed by Filter: 101125 Heap Fetches: 57059 Planning Time: 1.979 ms Execution Time: 65.704 ms
手动添加WHERE tenant_id = 2后,性能显著提升,执行计划显示数据库直接利用索引条件定位目标租户数据:
SET app.tenant_id = 2; SET ROLE user; EXPLAIN ANALYZE SELECT count(*) FROM people where tenant_id = 2; RESET ROLE; RESET app.tenant_id;
Aggregate (cost=1818.55..1818.56 rows=1 width=8) (actual time=13.079..13.080 rows=1 loops=1) -> Index Only Scan using index_people_on_tenant_id on people (cost=0.29..1810.77 rows=3112 width=0) (actual time=0.467..12.348 rows=6000 loops=1) Index Cond: (tenant_id = 2) Filter: (tenant_id = get_tenant_id()) Heap Fetches: 11826 Planning Time: 1.867 ms Execution Time: 15.641 ms
核心原因
问题出在get_tenant_id()函数的稳定性属性:
- PostgreSQL中,PL/pgSQL函数默认属性为
VOLATILE(易变),数据库认为该函数每次调用返回结果可能不同,无法在查询规划阶段确定其返回值。 - 因此RLS策略中的
USING (tenant_id = get_tenant_id())无法被规划器转换为可用于索引匹配的等值条件,只能在扫描完所有索引条目后逐行调用函数过滤。 - 手动添加的
WHERE tenant_id = 2是明确常量值,规划器可直接用它作为索引条件,快速定位目标数据。
解决方案
将get_tenant_id()函数属性改为STABLE(稳定)——同一个会话中app.tenant_id设置后不会改变,函数返回值在会话周期内固定:
CREATE OR REPLACE FUNCTION get_tenant_id() RETURNS BIGINT AS $$ BEGIN RETURN NULLIF(current_setting('app.tenant_id', TRUE), '')::BIGINT; END; $$ LANGUAGE plpgsql STABLE;
修改后,查询规划器会在会话开始时解析函数返回值,将RLS条件转换为与手动WHERE子句等价的索引匹配条件,大幅提升查询性能。
内容的提问来源于stack exchange,提问作者George Morris
相关产品推荐
相关产品推荐

