PostgreSQL为何无法自动内联策略函数?如何强制内联?
PostgreSQL权限策略中SECURITY DEFINER函数无法自动内联的原因及解决办法
为什么没法自动内联?
- SECURITY DEFINER的安全机制限制:SECURITY DEFINER函数的核心是用函数定义者的权限执行逻辑,要是自动内联,函数代码会直接融入主查询,执行时就会用当前用户的权限,完全违背了SECURITY DEFINER的设计目的。PostgreSQL为了守住这个安全边界,默认不会内联带SECURITY DEFINER属性的函数。
- PL/pgSQL函数的内联局限性:你写的
is_auth_in_org_of_alias是PL/pgSQL语言的,这种过程式语言的函数,PostgreSQL优化器很难解析并内联它的逻辑——哪怕没有SECURITY DEFINER,复杂PL/pgSQL函数的内联概率也极低。就算内层的SQL函数有机会内联,外层PL/pgSQL的嵌套也会把这条路堵死。 - 多层函数嵌套的阻碍:你是用外层PL/pgSQL函数调用内层SQL函数,这种嵌套结构让优化器没法穿透两层函数,把里面的关联查询逻辑直接合并到权限策略的主查询里,只能逐行调用函数做校验,自然慢得离谱。
有没有办法强制内联用回函数?
尝试调整函数属性(有限可行)
- 给SQL函数加
INLINEABLE属性:对于内层的is_auth_member_of_org,可以手动设置它允许内联,语法如下:
但要注意:内联后函数会用当前用户权限执行,要是你依赖SECURITY DEFINER的权限来访问ALTER FUNCTION is_auth_member_of_org(uuid) SET INLINEABLE = true;roles表,内联后可能会出现权限问题,得仔细测试。 - 把PL/pgSQL函数改成SQL函数:外层的
is_auth_in_org_of_alias是PL/pgSQL,没法加INLINEABLE,改成SQL函数才有机会被优化器处理:
不过就算这么改,因为有SECURITY DEFINER属性,优化器也不一定会100%内联,得看具体版本和查询复杂度。CREATE OR REPLACE FUNCTION is_auth_in_org_of_alias(_device_id UUID) RETURNS BOOLEAN SECURITY DEFINER LANGUAGE sql INLINEABLE AS $function$ SELECT is_auth_member_of_org( (SELECT organization_id FROM public.devices WHERE alias = _device_id) ); $function$;
全局调整优化器参数(不推荐)
可以修改from_collapse_limit和join_collapse_limit这两个参数,把默认值(一般是8)调大,比如设成32,让优化器更愿意合并子查询和函数逻辑。但这是全局设置,可能会让其他复杂查询的计划生成变慢,风险较高,除非你能确定只会影响目标查询。
更可靠的替代方案:用视图封装逻辑
如果强制内联的尝试不顺利,不如把权限校验逻辑封装成SECURITY DEFINER视图,这样既保留了封装性,优化器又能轻松把视图逻辑和主查询合并,避免逐行校验:
CREATE VIEW auth_org_device_access AS SELECT d.alias AS device_id FROM public.roles r JOIN public.devices d USING (organization_id) WHERE r.user_id = auth.uid() WITH LOCAL CHECK OPTION SECURITY DEFINER; -- 对应的权限策略 CREATE POLICY "Authenticated have full access to events table of devices in the orgs they are members in" ON events FOR ALL TO authenticated USING (device_id IN (SELECT device_id FROM auth_org_device_access)) WITH CHECK (device_id IN (SELECT device_id FROM auth_org_device_access));
内容的提问来源于stack exchange,提问作者Florian Loitsch
相关产品推荐
相关产品推荐

