PostgreSQL集返回函数调用优化

时间:2014-08-27 07:22:22

标签: postgresql optimization set-returning-functions

我对PostgreSQL 9.3有以下问题。

有一个视图将一个非平凡的查询封装到某些资源(例如,文档)。让我们说明它就像

一样简单
CREATE VIEW vw_resources AS
  SELECT * FROM documents; -- there are several joined tables in fact...

客户端应用程序通常在多个字段中使用视图,但通常会对结果进行分页,因此也可能会应用WHEREOFFSET

现在,在LIMIT计算的实际资源列表之上,我只想显示当前用户允许的资源。关于特权的规则相当复杂(它们取决于所讨论资源的几个属性,显式ACL,基于用户角色的隐式规则或与其他用户的关系......)所以我想将所有这些属性封装在一个单一功能。为了防止对每个资源进行重复的高成本查询,该函数获取资源ID列表,一次评估所有资源ID的权限,并返回所请求资源ID的集合以及相应的权限(区分读/写)。看起来大致如下:

vw_resources

问题是如何在CREATE FUNCTION list_privileges(resource_ids BIGINT[]) RETURNS TABLE (resource_id BIGINT, privilege TEXT) AS $function$ BEGIN -- the function lists privileges for a user that would get passed in an argument - omitting that for simplicity RAISE NOTICE 'list_privileges called'; -- for diagnostic purposes -- for illustration, let's simply grant write privileges for any odd resource: RETURN QUERY SELECT id, (CASE WHEN id % 2 = 1 THEN 'write' ELSE 'none' END) FROM unnest(resource_ids) id; END; $function$ LANGUAGE plpgsql STABLE; 视图中集成这样的函数,以便只提供用户有权使用的资源(即具有vw_resources'read'特权。 / p>

一个简单的解决方案将使用CTE:

'write'

问题是视图本身提供了太多行 - 一些CREATE VIEW vw_resources AS WITH base_data AS ( SELECT * FROM documents ) SELECT base_data.*, priv.privilege FROM base_data JOIN list_privileges((SELECT array_agg(resource_id) FROM base_data)) AS priv USING (resource_id) WHERE privilege IN ('read', 'write'); 条件和WHERE / OFFSET子句仅应用于视图本身,如LIMIT(客户端应用程序可能会请求任何复杂的过滤。由于PostgreSQL无法将条件推向CTE,SELECT * FROM vw_resources WHERE id IN (1,2,3) LIMIT 10函数最终会评估数据库中所有资源的权限,从而有效地降低了性能。

所以我试图使用一个窗口函数来收集整个结果集中的资源ID,并在外部查询中加入list_privileges(BIGINT[])函数,如下图所示,但list_privileges(BIGINT[])函数最终成为每一行重复调用(由'list_privileges称为'通知证明),这有点破坏了之前的努力:

list_privileges(BIGINT[])

我会强迫客户端提供两个单独的查询,第一个是CREATE VIEW vw_resources AS SELECT d.*, priv.privilege FROM ( SELECT *, array_agg(resource_id) OVER () AS collected FROM documents ) AS d JOIN list_privileges(d.collected) AS priv USING (resource_id) WHERE privilege IN ('read', 'write'); 没有应用权限,第二个调用vw_resources函数传递第一个查询获取的资源ID列表,并过滤客户端上不允许的资源。然而,对于客户来说这是非常笨拙的,并且例如获得前20个允许的资源实际上是不可能的,因为限制第一个查询根本就没有得到它 - 如果某些资源因特权而被过滤掉,那么我们在整体结果中根本没有20行...

欢迎任何帮助!

P.S。为了完整起见,我附加了一个样本list_privileges(BIGINT[])表:

documents

1 个答案:

答案 0 :(得分:2)

如果必须使用plpgsql,则创建不带参数的函数

create function list_privileges()
  returns table (resource_id bigint, privilege text)
as $function$
begin
  raise notice 'list_privileges called'; -- for diagnostic purposes
  return query select 1, case when 1 % 2 = 1 then 'write' else 'none' end
  ;
end;
$function$ language plpgsql stable;

将其加入其他复杂查询以形成vw_resources视图

create view vw_resources as
select *
from
    documents d
    inner join
    list_privileges() using(resource_id)

过滤条件将在查询时添加

select *
from vw_resources
where
    id in (1,2,3)
    and
    privilege in ('read', 'write')

让规划师完成其优化魔术并在任何“过早优化”之前检查explain输出。

这只是一个猜想:该功能可能会使规划人员难以优化。

如果plpgsql不是真的有必要,并且非常频繁,我只会创建一个视图而不是函数

create view vw_list_privileges as
select
    1 as resource_id,
    case when 1 % 2 = 1 then 'write' else 'none' end as privilege

以与复杂查询相同的方式加入

create view vw_resources as
select *
from
    documents d
    inner join
    vw_list_privileges using(resource_id)