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

Ste*_*orn 3 postgresql postgresql-9.1

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

select *
from table_name
where id = 1234
Run Code Online (Sandbox Code Playgroud)

...运行时间不到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;
Run Code Online (Sandbox Code Playgroud)

执行此功能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
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 7

PostgreSQL 9.2中的更新

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

即使使用预准备语句,也允许计划程序为特定参数值生成自定义计划(Tom Lane)

过去,准备好的语句总是有一个用于所有参数值的"通用"计划,这通常远远低于用于包含显式常量值的非预处理语句的计划.现在,计划程序尝试为特定参数值生成自定义计划.只有在反复证明自定义计划不能提供任何好处之后,才能使用通用计划.此更改应消除以前使用预准备语句(包括PL/pgSQL中的非动态语句)所看到的性能损失.


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

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

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

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

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;
Run Code Online (Sandbox Code Playgroud)

评论后编辑:

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

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

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

  • 在查阅了pgsql-performance列表后,我得到了另一个正确答案.匹配的列在我的数据库表中定义为字符变化(9),但函数的输入参数定义为字符.更改函数参数类型以匹配列定义使我的函数执行与通过命令行一样好.Erwin Brandstetter的回答在技术上也是正确的,所以我将其标记为答案. (2认同)