PostgreSQL 教程: 调整函数的估计执行成本

二月 17, 2024

摘要:在本教程中,您将学习如何调整函数的估计执行成本。

目录

介绍

当您在 PostgreSQL 中引用/调用函数时,优化器实际上不太了解执行的成本和函数返回的行数。这并不奇怪,因为很难预测函数在做什么,以及给定的一组输入参数将返回多少行。您可能不知道的是,其实您可以告诉优化器更多有关您的函数的成本信息。

PostgreSQL 为函数提供了 ROWS 和 COST 声明。它们可用于用任何语言编写的任何 PostgreSQL 函数。这些声明允许函数设计者向规划器指示预期的行数,并提供有关函数调用的开销的提示。COST 以 CPU 周期为单位。COST 数值越高,成本越高。例如,如果在 WHERE 子句中的 AND 表达式中,有任何成本较低的函数计算返回逻辑否的结果,则不会再调用成本高的函数。ROWS 和 COST 的数值可以让规划器更好地选择使用哪种策略。

在 WHERE 子句中使用函数

让我们开始设置一个小的测试用例:

CREATE TABLE t1 (id integer, str text);

INSERT INTO t1 (id, str)
SELECT a.i, repeat(chr(65 + mod(a.r, 26)), 10 + mod(a.r, 100)) as str
FROM
  (SELECT i, CAST(random() * 1000 AS int) + 1 AS r
     FROM generate_series(1, 1000) AS s(i)) AS a;

创建了一个包含 1000 行的简单表。此外,让我们再创建 2 个简单的函数:

CREATE FUNCTION fn_cheapfunction(id in int)
  RETURNS integer AS $$
BEGIN
 RETURN mod(id, 10);
END $$ LANGUAGE plpgsql;

CREATE FUNCTION fn_costlyfunction(str in text)
  RETURNS integer AS $$
BEGIN
 PERFORM pg_sleep(0.01); -- make this function slower
 RETURN length(str);
END $$ LANGUAGE plpgsql;

当您在 WHERE 子句中调用这些函数时,优化器会做什么?

EXPLAIN (analyze) SELECT count(*) FROM t1
  WHERE (fn_costlyfunction(str) > 80 AND fn_cheapfunction(id) > 8);
                                                 QUERY PLAN
-------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=526.28..526.29 rows=1 width=8) (actual time=15710.681..15710.681 rows=1 loops=1)
   ->  Seq Scan on t1  (cost=0.00..526.00 rows=111 width=0) (actual time=293.123..15710.539 rows=27 loops=1)
         Filter: ((fn_costlyfunction(str) > 80) AND (fn_cheapfunction(id) > 8))
         Rows Removed by Filter: 973
 Planning time: 2.181 ms
 Execution time: 15714.909 ms
(6 rows)

指定函数的估计成本

PostgreSQL 为函数定义提供了一个 COST 参数。这是一个正数,用于指定函数的估计执行成本,以 cpu_operator_cost 为单位。如果函数返回一个集合,那它是返回每行的成本。如果未指定成本,则 C 语言函数和内部函数默认为 1 个单位,所有其他语言的函数默认为 100 个单位。更大的值会导致规划器尝试避免对该函数的不必要的过多计算。

要更改上面的函数的估计执行成本,请执行以下操作:

ALTER FUNCTION fn_cheapfunction(id in int) COST 10;      -- a lower cost
ALTER FUNCTION fn_costlyfunction(str in text) COST 1000; -- a higher cost

规划器会检测到fn_costlyfunction()执行开销大,并首先运行fn_cheapfunction(),即使它在顺序上是第二个出现的函数。再来看一下执行计划:

EXPLAIN (analyze) SELECT count(*) FROM t1
  WHERE (fn_costlyfunction(str) > 80 AND fn_cheapfunction(id) > 8);
                                                 QUERY PLAN
------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=2776.28..2776.29 rows=1 width=8) (actual time=1575.873..1575.873 rows=1 loops=1)
   ->  Seq Scan on t1  (cost=0.00..2776.00 rows=111 width=0) (actual time=36.927..1575.726 rows=27 loops=1)
         Filter: ((fn_cheapfunction(id) > 8) AND (fn_costlyfunction(str) > 80))
         Rows Removed by Filter: 973
 Planning time: 0.142 ms
 Execution time: 1575.958 ms
(6 rows)

现在,查询的运行速度比之前快多了。

了解更多

PostgreSQL 优化