4

我有一个非常简单的查询,它并不比:

select *
from table_name
where id = 1234

...运行时间不到 50 毫秒。

接受该查询并将其放入一个函数中:

CREATE OR REPLACE FUNCTION pie(id_param integer)
RETURNS SETOF record AS
$BODY$
BEGIN
    RETURN QUERY SELECT *
         FROM table_name
         where id = id_param;
END
$BODY$
LANGUAGE plpgsql STABLE;

此函数执行时select * from pie(123);需要 22 秒。

如果我硬编码一个整数来代替 id_param,该函数将在 50 毫秒内执行。

为什么我在 where 语句中使用参数会导致我的函数运行缓慢?


编辑以添加具体示例:

CREATE TYPE test_type AS (gid integer, geocode character varying(9))

CREATE OR REPLACE FUNCTION geocode_route_by_geocode(geocode_param character)
  RETURNS SETOF test_type AS
$BODY$
BEGIN
RETURN QUERY EXECUTE
    'SELECT     gs.geo_shape_id AS gid,     
        gs.geocode
    FROM geo_shapes gs
    WHERE geocode = $1
    AND geo_type = 1 
    GROUP BY geography, gid, geocode' USING geocode_param;
END;

$BODY$
  LANGUAGE plpgsql STABLE;
ALTER FUNCTION geocode_carrier_route_by_geocode(character)
  OWNER TO root;

--Runs in 20 seconds
select * from geocode_route_by_geocode('999xyz');

--Runs in 10 milliseconds
SELECT  gs.geo_shape_id AS gid,     
        gs.geocode
    FROM geo_shapes gs
    WHERE geocode = '9999xyz'
    AND geo_type = 1 
    GROUP BY geography, gid, geocode
4

1 回答 1

9

PostgreSQL 9.2 更新

有一个重大改进,我在这里引用发行说明

即使在使用准备好的语句时,也允许计划器为特定参数值生成自定义计划 (Tom Lane)

过去,准备好的语句总是有一个用于所有参数值的“通用”计划,这通常比用于包含显式常量值的非准备语句的计划差很多。现在,计划器尝试为特定参数值生成自定义计划。只有在定制计划反复证明没有任何好处后,才会使用通用计划。此更改应消除以前使用准备好的语句(包括 PL/pgSQL 中的非动态语句)所带来的性能损失。


PostgreSQL 9.1 或更早版本的原始答案

plpgsql 函数具有与PREPARE语句类似的效果:解析查询并缓存查询计划。

优点是每次调用都节省了一些开销。
缺点是查询计划没有针对调用它的特定参数值进行优化。

对于具有均匀数据分布的表的查询,这通常不会有问题,并且 PL/pgSQL 函数的执行速度将比原始 SQL 查询或 SQL 函数快一些。但是,如果您的查询可以根据WHERE子句中的实际值使用某些索引,或者更一般地,为特定值选择更好的查询计划,那么您最终可能会得到一个次优的查询计划。尝试使用 SQL 函数或使用动态 SQLEXECUTE来强制为每次调用重新计划查询。可能看起来像这样:

CREATE OR REPLACE FUNCTION pie(id_param integer)
RETURNS SETOF record AS
$BODY$
BEGIN        
    RETURN QUERY EXECUTE
        'SELECT *
         FROM   table_name
         where  id = $1'
    USING id_param;
END
$BODY$
LANGUAGE plpgsql STABLE;

评论后编辑:

如果这个变体没有改变执行时间,那么肯定还有其他你可能错过或没有提到的因素在起作用。不同的数据库?不同的参数值?您将不得不发布更多详细信息。

我从手册中添加了一个引用来支持我的上述陈述:

一个带有简单常量命令字符串和一些 USING 参数的 EXECUTE,如上面的第一个示例,在功能上等同于直接在 PL/pgSQL 中编写命令并允许自动替换 PL/pgSQL 变量。重要的区别在于 EXECUTE 将在每次执行时重新规划命令,生成特定于当前参数值的计划;而 PL/pgSQL 通常会创建一个通用计划并将其缓存以供重复使用。在最佳计划强烈依赖于参数值的情况下,EXECUTE 可以明显更快;而当计划对参数值不敏感时,重新计划将是一种浪费。

于 2012-02-16T03:49:48.363 回答