【问题标题】:Execute multiple functions together without losing performance一起执行多个功能而不会损失性能
【发布时间】:2015-08-01 19:03:33
【问题描述】:

我有这个过程必须使用 pl/pgsql 进行一系列查询:

--process:
SELECT function1();
SELECT function2();
SELECT function3();
SELECT function4();

为了能够在一次调用中执行所有操作,我创建了一个这样的流程函数:

CREATE OR REPLACE FUNCTION process()
  RETURNS text AS
$BODY$
BEGIN
    PERFORM function1();
    PERFORM function2();
    PERFORM function3();
    PERFORM function4();
    RETURN 'process ended';
END;
$BODY$
  LANGUAGE plpgsql

问题是,当我把每个函数本身所用的时间相加时,总共是200秒,而函数process()所用的时间是一个多小时!

可能是内存问题,但我不知道应该更改postgresql.conf 上的哪个配置。

数据库在 Debian 8 中的 PostgreSQL 9.4 上运行。

【问题讨论】:

  • 也许perform 会导致不同的查询计划?试试declare MyVar int; begin MyVar := (select function1()); 之类的?
  • 如果你选择 function1(), function2(), function3(), function4();然后 Postgresql 将能够并行运行所有四个函数。这并不意味着它会,但至少它可以。
  • @Andomar 我试过了,没解决:(
  • 你能粗略地勾勒一下这些函数的作用吗?如果他们用了 200 秒,那么他们显然在做一些不平凡的工作。
  • 我想我知道为什么会发生这种情况,但不知道如何避免:Postgres 函数周围有一个隐式事务,因此必须做额外的工作来维护日志或锁跨越所有 4 个内部函数。 (您可以通过在显式事务中定时运行所有 4 个函数来检查这是否是原因。)

标签: postgresql plpgsql database-performance query-performance postgresql-performance


【解决方案1】:

您评论说这 4 个函数必须连续运行。因此可以安全地假设每个函数都使用已被前一个函数修改的表中的数据。那是我的主要嫌疑人。

任何 Postgres 函数都在外部上下文的事务中运行。因此,如果打包到另一个函数中,所有函数共享相同的事务上下文。显然,每个人都可以看到对以前功能的数据的影响。 (即使其他并发事务仍然看不到效果。)但统计信息不会立即更新。

查询计划基于涉及对象的statistics。 PL/pgSQL 在实际执行之前不会计划语句,这对您有利。 Per documentation:

在函数中首先执行每个表达式和 SQL 命令, PL/pgSQL 解释器解析和分析命令以创建一个 准备好的语句,使用 SPI 管理器的 SPI_prepare 函数。

PL/pgSQL可以缓存查询计划,但只能在同一个会话中和(在 pg 9.2 + 至少)只有在几次执行显示相同的查询计划重复工作最好之后。如果您怀疑这对您来说有问题,您可以使用动态 SQL 来解决它,它每次都会强制执行新计划:

EXECUTE 'SELECT function1()';

但是,我看到的最有可能的候选者是导致查询计划较差的无效统计信息。 SELECT / PERFORM 函数内的语句(相同的东西)快速连续运行,autovacuum 没有机会在一个函数和下一个函数之间启动和更新统计信息。如果一个函数大量改变了下一个函数正在使用的表中的数据,那么下一个函数可能会根据过时的信息来制定查询计划。典型示例:一个只有几行的表填充了数千行,但下一个计划仍然认为顺序扫描对于“小”表来说是最快的。你说:

当我将每个函数单独花费的时间相加时,总和是 200 秒,而函数 process() 花费的时间更多 超过一小时!

究竟是什么意思?您是在单个事务中运行它们还是在单个事务中运行它们?甚至可能中间有一段时间?这将允许 autovacuum 更新统计信息(通常相当快),并可能根据更改的统计信息导致完全不同的查询计划。

您可以使用auto-explain检查查询计划inside plpgsql 函数

如果您能识别出此类问题,您可以在语句之间强制使用ANALYZE。在这方面,对于几个 SELECT / PERFORM 语句,您不妨使用更简单的 SQL 函数 并完全避免计划缓存(但见下文!):

CREATE OR REPLACE FUNCTION process()
  RETURNS text
  LANGUAGE sql AS
$func$
   SELECT function1();

   ANALYZE some_substantially_affected_table;

   SELECT function2();
   SELECT function3();

   ANALYZE some_other_table;

   SELECT function4();
   SELECT 'process ended';  -- only last result is returned
$func$;

此外,只要我们没有看到您调用的函数的实际代码,就可能有任何数量的其他隐藏效果
示例:您可以SET LOCAL ... 一些配置参数来提高您的function1() 的性能。如果在不会影响其余事务的单独事务中调用。效果只持续到交易结束。但如果在单个事务中调用它也会影响其余事务...

基础知识:

另外:事务累积锁,这会绑定越来越多的资源,并可能导致与并发进程的摩擦增加。所有锁都在事务结束时释放。 如果可能的话,最好在单独的事务中运行大函数,而不是包装在单个函数中(因此也包含在事务中)。最后一项与@klinIMSoP 已经涵盖的内容有关。

【讨论】:

  • 在 Postgres 早期版本(可能在 9.4 之前)中,事务内的 ANALYZE 可能会在事务回滚时损坏表。
  • @klin:当您说“损坏表”时,您实际上是指系统目录中描述回滚状态的不准确统计信息?在此过程中不会损坏实际表。曾经。此外,这不是问题,因为至少 Postgres 9.3:postgresql.org/message-id/flat/…
  • 致@Erwin。不,我的意思是this
  • @klin:我明白你的意思。这是一个罕见的极端情况,但该修复已回补到所有受支持的版本:9.4.1、9.3.6、9.2.10、9.1.15 和 9.0.19(分别记录在 release notes 中)。关注project's policy 的每个人都可以始终为每个主要版本运行最新的可用次要版本。
  • 致@Erwin。函数内部的 ANALYZE 也是一种罕见的极端情况。在我看来,这不会带来任何结果。一小时的函数执行不是由于计划不当造成的,而是由于内存不足。有几次,我有机会通过将不必要的复杂事务共享给独立的部分来治愈类似的案例。 OP 试图做的只是对服务器效率的犯罪
【解决方案2】:

对未来读者的警告 (2015-05-30)。

问题中描述的技术是有效阻止服务器的最聪明的方法之一。

在一些公司中,使用这项技术可以获得立即终止雇佣合同的形式的奖励。

尝试改进此方法是无用的。它简单、美观且足够有效。


在 RDMS 中,对事务的支持非常昂贵。执行事务时,服务器必须创建并存储有关对数据库所做的所有更改的信息,以使这些更改在成功完成的情况下(其他并发进程)在环境中可见,并在失败的情况下恢复事务之前的状态尽早。因此影响服务器性能的自然原则是在一个事务中包含最少数量的数据库操作,即。只在必要的范围内。

一个 Postgres 函数在一个事务中执行。在其中放置许多可以独立运行的操作是严重违反上述规则的。

答案很简单:不要这样做。函数执行不仅仅是脚本的执行。

在用于编写应用程序的过程语言中,还有许多其他可能性可以通过使用函数或脚本来简化代码。也可以使用 shell 运行脚本。

如果有可能在函数中使用事务,则为此目的使用 Postgres 函数是有意义的。目前,这种可能性不存在,尽管关于这个问题的讨论已经有很长的历史了(你可以阅读它,例如postgres mailing lists)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-23
    • 1970-01-01
    • 1970-01-01
    • 2019-02-09
    • 1970-01-01
    • 2019-12-23
    相关资源
    最近更新 更多