Postgresql 与没有函数包装器的查询相比,SQL函数非常慢

Postgresql 与没有函数包装器的查询相比,SQL函数非常慢,postgresql,function,postgresql-performance,sql-execution-plan,Postgresql,Function,Postgresql Performance,Sql Execution Plan,我有一个运行速度非常快的PostgreSQL 9.4查询(~12ms): 但是如果我把它嵌入到一个函数中,查询在所有数据中运行得非常慢,似乎在每个记录中运行,我缺少了什么?我有大约1M的数据,我想简化我的数据库层,将大型查询存储到函数和视图中 CREATE OR REPLACE FUNCTION get_web_events_by_userid(int) RETURNS TABLE( id int, time_stamp timestamp with time zone,

我有一个运行速度非常快的PostgreSQL 9.4查询(~12ms):

但是如果我把它嵌入到一个函数中,查询在所有数据中运行得非常慢,似乎在每个记录中运行,我缺少了什么?我有大约1M的数据,我想简化我的数据库层,将大型查询存储到函数和视图中

CREATE OR REPLACE FUNCTION get_web_events_by_userid(int) RETURNS TABLE(
    id int,
    time_stamp timestamp with time zone,
    description text,
    origin text,
    userlogin text,
    customer text,
    client_ip inet
     ) AS
$func$
SELECT 
  auth_web_events.id, 
  auth_web_events.time_stamp, 
  auth_web_events.description, 
  auth_web_events.origin,  
  auth_user.email AS user, 
  customers.name AS customer,
  auth_web_events.client_ip
FROM 
  public.auth_web_events, 
  public.auth_user, 
  public.customers
WHERE 
  auth_web_events.user_id_fk = auth_user.id AND
  auth_user.customer_id_fk = customers.id AND
  auth_web_events.user_id_fk = $1
ORDER BY
  auth_web_events.id DESC;
  $func$ LANGUAGE SQL;
查询计划是:

"Sort  (cost=20.94..20.94 rows=1 width=791) (actual time=61.905..61.906 rows=2 loops=1)"
"  Sort Key: auth_web_events.id"
"  Sort Method: quicksort  Memory: 25kB"
"  ->  Nested Loop  (cost=0.85..20.93 rows=1 width=791) (actual time=61.884..61.893 rows=2 loops=1)"
"        ->  Nested Loop  (cost=0.71..12.75 rows=1 width=577) (actual time=61.874..61.879 rows=2 loops=1)"
"              ->  Index Scan using auth_web_events_fk1 on auth_web_events  (cost=0.57..4.58 rows=1 width=61) (actual time=61.860..61.860 rows=2 loops=1)"
"                    Index Cond: (user_id_fk = 2)"
"              ->  Index Scan using auth_user_pkey on auth_user  (cost=0.14..8.16 rows=1 width=524) (actual time=0.005..0.005 rows=1 loops=2)"
"                    Index Cond: (id = 2)"
"        ->  Index Scan using customers_id_idx on customers  (cost=0.14..8.16 rows=1 width=222) (actual time=0.004..0.005 rows=1 loops=2)"
"              Index Cond: (id = auth_user.customer_id_fk)"
"Planning time: 0.369 ms"
"Execution time: 61.965 ms"
我这样调用函数:

SELECT * from get_web_events_by_userid(2)  
该功能的查询计划:

"Function Scan on get_web_events_by_userid  (cost=0.25..10.25 rows=1000 width=172) (actual time=279107.142..279107.144 rows=2 loops=1)"
"Planning time: 0.038 ms"
"Execution time: 279107.175 ms"
编辑:我只是更改了参数,问题仍然存在。
EDIT2:Erwin答案的查询计划:

"Sort  (cost=20.94..20.94 rows=1 width=791) (actual time=0.048..0.049 rows=2 loops=1)"
"  Sort Key: w.id"
"  Sort Method: quicksort  Memory: 25kB"
"  ->  Nested Loop  (cost=0.85..20.93 rows=1 width=791) (actual time=0.030..0.037 rows=2 loops=1)"
"        ->  Nested Loop  (cost=0.71..12.75 rows=1 width=577) (actual time=0.023..0.025 rows=2 loops=1)"
"              ->  Index Scan using auth_user_pkey on auth_user u  (cost=0.14..8.16 rows=1 width=524) (actual time=0.011..0.012 rows=1 loops=1)"
"                    Index Cond: (id = 2)"
"              ->  Index Scan using auth_web_events_fk1 on auth_web_events w  (cost=0.57..4.58 rows=1 width=61) (actual time=0.008..0.008 rows=2 loops=1)"
"                    Index Cond: (user_id_fk = 2)"
"        ->  Index Scan using customers_id_idx on customers c  (cost=0.14..8.16 rows=1 width=222) (actual time=0.003..0.004 rows=1 loops=2)"
"              Index Cond: (id = u.customer_id_fk)"
"Planning time: 0.541 ms"
"Execution time: 0.101 ms"

通过使此查询动态化并使用plpgsql,您将获得更好的性能

CREATE OR REPLACE FUNCTION get_web_events_by_userid(uid int) RETURNS TABLE(
    id int,
    time_stamp timestamp with time zone,
    description text,
    origin text,
    userlogin text,
    customer text,
    client_ip inet
     ) AS $$
BEGIN

RETURN QUERY EXECUTE
'SELECT 
  auth_web_events.id, 
  auth_web_events.time_stamp, 
  auth_web_events.description, 
  auth_web_events.origin,  
  auth_user.email AS user, 
  customers.name AS customer,
  auth_web_events.client_ip
FROM 
  public.auth_web_events, 
  public.auth_user, 
  public.customers
WHERE 
  auth_web_events.user_id_fk = auth_user.id AND
  auth_user.customer_id_fk = customers.id AND
  auth_web_events.user_id_fk = ' || uid ||
'ORDER BY
  auth_web_events.id DESC;'

END;
$$ LANGUAGE plpgsql;
用户

在重写函数时,我意识到您在此处添加了列别名:

SELECT 
  ...
  auth_user.email AS user, 
  customers.name AS customer,
显然,
STABLE
关键字改变了结果。在您描述的测试情况下,这不应该是一个问题。该设置通常不利于单个单独的函数调用。另请阅读,标准的
EXPLAIN
不会显示函数内部的查询计划。您可以使用附加模块“自动解释”:

您有一个非常奇怪的数据分布:

auth\u web\u events表有100000000条记录,auth\u user->2条记录,customers->1条记录


由于没有进行其他定义,该函数假定返回的估计值为1000行。但是您的函数实际上只返回2行。如果所有调用只返回(在附近)2行,只需添加
第2行
即可。可能还会更改
VOLATILE
变量的查询计划(即使
STABLE
在这里是正确的选择)。

第一个变量的查询计划是什么?它使用索引吗?@jpmc26:我不同意你的建议。如果操作正确,将大型查询放入函数中可能非常有用。在数据库中维护函数通常更方便,因为在数据库中跟踪依赖关系更容易。这样通常速度更快。应用程序不必为每个会话准备复杂的查询,除此之外,还需要发送一个长的查询字符串,而不仅仅是一个简单的函数调用。最好的行动方案取决于整个情况。我刚刚添加了查询计划…@jpmc26:你一直声称“增加了复杂性”,我看到了降低复杂性的潜力。应用程序不必准备(或者更糟糕的是,连接)查询,只需调用存储过程即可。您最喜欢的标记是python,您的参数反映了该技能。我的主要专长是研究生,我有不同的观点。您是基于您的观点而不是(未知)用例的实际需求来概括声明的。这是一种常见的模式。另一个需要检查的是
auth\u web\u events.user\u id\u fk
实际上是
INT
列?(听起来很奇怪,我知道,但值得肯定。)嗯,这真的会返回什么?您是否必须使用
返回查询
?我想这可能会影响您的结果。我不知道它是否可以优化查询执行,但显然,您应该重新验证性能是否更好。我在“选择”第13行或其附近收到:错误:语法错误:选择^*********错误******错误:语法错误,或其附近的“选择”SQL状态:42601字符:268@pwnyexpress看见正如阿尔昌所指出的那样。从9.2版开始,无论查询是否在任何类型的函数中,都应该重新规划查询。这一点都不必要。这里只需要一个简单的SQL函数。如果没有动态SQL,PL/pgSQL可能会很有用,因为它将查询视为一条准备好的语句(重用查询计划),但这与当前的问题完全无关。简言之:这个答案是误导性的,基本上是错误的。此外,在使用动态SQL时,最好使用
子句传递值参数,而不是连接文本表示。问题似乎仍然存在:“按用户ID(成本=0.25..10.25行=1000宽度=172)(实际时间=250263.587..250263.587行=2个循环=1)对get_web_事件进行函数扫描”计划时间:0.036毫秒“执行时间:250263.612毫秒“auth_web_事件表有100000000条记录,auth_用户->2条记录,客户->1record@Mmeyer:很高兴它能工作
STABLE
在这里是正确的设置,在更大的查询上下文中,重复调用可能会受益。但是它不应该对您的独立测试用例产生影响。我在答案上加了一点。@ErwinBrandstetter Hm。在我发表我的评论后,我能够通过一个非常简单的函数,在
VOLATILE
STABLE
之间切换,重现
EXPLAIN
的行为。我是在psql中这样做的,在一些
auto_explain
的配置参数上显示了“无法识别”的错误。所以我很确定
auto_explain
没有加载或启用。我是9.3。如果有兴趣的话,我可以发布一个问题。还有一个奇怪的问题:当它没有显示函数的内部平面图时,我得到
行=1000
(与OP相同)。我的函数最多可以返回5行,当它显示内部计划时,它可以正确估计5行。我还看到运行时比
EXPLAIN ANALYZE
输出增加了大约3倍。(查询太快了,但我不确定这是否有意义。)计划者真的能放弃函数调用而将查询作为子查询内联吗?这将解释很多。@ErwinBrandstetter仅供参考,但我在研究一个非常类似的问题时发现了这个问题和答案。我有一个查询运行了大约91毫秒,当我把它放到一个函数中时,它跳到了4900毫秒以上。添加
STABLE
使其执行类似于原始SQL。
SELECT 
  ...
  auth_user.email AS user, 
  customers.name AS customer,
CREATE OR REPLACE FUNCTION get_web_events_by_userid(int)
  RETURNS TABLE(
     id int
   , time_stamp timestamptz
   , description text
   , origin text
   , userlogin text
   , customer text
   , client_ip inet
  ) AS
$func$
SELECT w.id
     , w.time_stamp
     , w.description 
     , w.origin  
     , u.email     -- AS user   -- make this a comment!
     , c.name      -- AS customer
     , w.client_ip
FROM   public.auth_user       u
JOIN   public.auth_web_events w ON w.user_id_fk = u.id
JOIN   public.customers       c ON c.id = u.customer_id_fk 
WHERE  u.id = $1   -- reverted the logic here
ORDER  BY w.id DESC
$func$ LANGUAGE sql STABLE;