为什么PostgreSQL在函数中以不同的方式处理我的查询?

时间:2012-02-16 03:26:50

标签: postgresql postgresql-9.1

我有一个非常简单的查询,它不比以下复杂得多:

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

1 个答案:

答案 0 :(得分:7)

PostgreSQL 9.2中的更新

有一个重大改进,我引用了release notes here

  

允许规划人员为特定参数生成自定义计划   即使使用准备好的语句(Tom Lane)

也是如此      

过去,准备好的声明总是有一个“通用”计划   用于所有参数值,这通常很多   不如用于包含的非预备陈述的计划   显式常量值。现在,计划者试图生成自定义   计划特定的参数值。只会使用通用计划   经过多次证明定制计划无法提供任何好处。这个   改变应该消除以前看到的绩效惩罚   使用准备好的陈述(包括非动态陈述)   PL / pgSQL的)。


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

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

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

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

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;

评论后编辑:

如果此变体不会改变执行时间,则必须有其他因素可能已经错过或未提及。不同数据库?不同的参数值?您必须发布更多详细信息。

我添加引用from the manual来支持我的上述陈述:

  

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