【问题标题】:Configuration parameter work_mem in PostgreSQL on LinuxLinux 上 PostgreSQL 中的配置参数 work_mem
【发布时间】:2011-12-27 17:12:11
【问题描述】:

我必须通过调整基本的 PostgreSQL 服务器配置参数来优化查询。在文档中,我遇到了 work_mem 参数。然后我检查了更改此参数将如何影响我的查询性能(使用排序)。我用各种work_mem 设置测量了查询执行时间,结果非常失望。

我在其上执行查询的表包含 10,000,000 行,并且有 430 MB 的数据要排序。 (Sort Method: external merge Disk: 430112kB)。

使用work_mem = 1MB,EXPLAIN 输出为:

Total runtime: 29950.571 ms (sort takes about 19300 ms).
Sort  (cost=4032588.78..4082588.66 rows=19999954 width=8) 
(actual time=22577.149..26424.951 rows=20000000 loops=1)
                 Sort Key: "*SELECT* 1".n
                 Sort Method:  external merge  Disk: 430104kB

与work_mem = 5MB:

Total runtime: 36282.729 ms (sort: 25400 ms).
Sort  (cost=3485713.78..3535713.66 rows=19999954 width=8) 
      (actual time=25062.383..33246.561 rows=20000000 loops=1)
      Sort Key: "*SELECT* 1".n
      Sort Method:  external merge  Disk: 430104kB

与work_mem = 64MB:

Total runtime: 42566.538 ms (sort: 31000 ms).
Sort  (cost=3212276.28..3262276.16 rows=19999954 width=8) 
(actual time=28599.611..39454.279 rows=20000000 loops=1)
                 Sort Key: "*SELECT* 1".n
                 Sort Method:  external merge  Disk: 430104kB

谁能解释为什么性能会变差?或者建议任何其他方法通过更改服务器参数来加快查询执行速度?

我的查询(我知道它不是最优的,但我必须对这种查询进行基准测试):

SELECT n
FROM   (
    SELECT n + 1 AS n FROM table_name
    EXCEPT
    SELECT n FROM table_name) AS q1
ORDER BY n DESC;

完整的执行计划:

Sort  (cost=5805421.81..5830421.75 rows=9999977 width=8) (actual time=30405.682..30405.682 rows=1 loops=1)
Sort Key: q1.n
Sort Method:  quicksort  Memory: 25kB
->  Subquery Scan q1  (cost=4032588.78..4232588.32 rows=9999977 width=8) (actual time=30405.636..30405.637 rows=1 loops=1)
    ->  SetOp Except  (cost=4032588.78..4132588.55 rows=9999977 width=8) (actual time=30405.634..30405.634 rows=1 loops=1)
           ->  Sort  (cost=4032588.78..4082588.66 rows=19999954 width=8) (actual time=23046.478..27733.020 rows=20000000 loops=1)
                 Sort Key: "*SELECT* 1".n
                 Sort Method:  external merge  Disk: 430104kB
                 ->  Append  (cost=0.00..513495.02 rows=19999954 width=8) (actual time=0.040..8191.185 rows=20000000 loops=1)
                       ->  Subquery Scan "*SELECT* 1"  (cost=0.00..269247.48 rows=9999977 width=8) (actual time=0.039..3651.506 rows=10000000 loops=1)
                             ->  Seq Scan on table_name  (cost=0.00..169247.71 rows=9999977 width=8) (actual time=0.038..2258.323 rows=10000000 loops=1)
                       ->  Subquery Scan "*SELECT* 2"  (cost=0.00..244247.54 rows=9999977 width=8) (actual time=0.008..2697.546 rows=10000000 loops=1)
                             ->  Seq Scan on table_name  (cost=0.00..144247.77 rows=9999977 width=8) (actual time=0.006..1079.561 rows=10000000 loops=1)
Total runtime: 30496.100 ms

【问题讨论】:

  • 在增加 workmem 时,其中一个子查询中是否有另一个合并,从外部合并或嵌套循环或索引循环转移到 hashmap?
  • 我已经编辑了我的帖子并包含了查询和执行计划。
  • 您的查询与 EXPLAIN ANALYZE 输出不匹配。你使这比它需要的更难。此外,您可能想知道:只有 OP 会自动收到评论提醒。其他人你必须像@Grzes 这样明确地解决。但有一些限制适用。在这里阅读更多:meta.stackexchange.com/questions/43019/…
  • @Erwin:不匹配,因为我在查询中更改了表名和参数名。 (我会纠正它)。但查询计划与查询相关。

标签: postgresql postgresql-performance server-configuration


【解决方案1】:

我在explain.depesz.com, have a look 上发布了您的查询计划。

查询规划器的估计在某些地方是非常错误的。 你最近跑过ANALYZE吗?

阅读手册中关于Statistics Used by the Planner 和Planner Cost Constants 的章节。特别注意random_page_cost和default_statistics_target的章节。
你可以试试:

ALTER TABLE diplomas ALTER COLUMN number SET STATISTICS 1000;
ANALYZE diplomas;

对于有 1000 万行的表,甚至更高。这取决于数据分布和实际查询。实验。默认为 100,最大为 10000。

对于这种大小的数据库,只有 1 或 5 MB 的 work_mem 通常是不够的。阅读@aleroot 链接到的Postgres Wiki page on Tuning Postgres。

根据EXPLAIN 的输出,您的查询需要430104kB 的磁盘内存,您必须将work_mem 设置为500MB 或更多以允许输入-记忆排序。数据的内存表示比磁盘表示需要更多的空间。您可能对Tom Lane posted on that matter recently 感兴趣。

将work_mem 增加一点,就像您尝试过的那样,不会有太大帮助,甚至会减慢速度。将其全局设置为高甚至可能会受到伤害,尤其是在并发访问时。多个会话可能会互相饿死资源。如果资源有限,为一个目的分配更多内存会占用另一个目的的内存。最佳设置取决于完整的情况。

为避免副作用,请仅在会话中将其设置得足够高,并暂时用于查询:

SET work_mem = '500MB';

之后将其重置为默认值:

RESET work_mem;

或使用SET LOCAL 将其设置为仅用于当前事务的开始。

【讨论】:

  • 是的@Erwin,我已经运行了 VACUUM ANALYZE。统计数据是最新的。在您撰写帖子之前,我还使用 work_mem = 450MB(19.5 秒而不是 30 秒)执行了查询。但是如此巨大的 work_mem 值可能很危险。我读过可以执行许多并行操作(排序、哈希),因此所需的总内存成本可能是 n * 500MB,并且可能超过 ram 内存的数量。感谢您的链接。
  • @Grzes 如果您像我建议的那样只为您的查询设置work_mem,您可以控制使用多少内存。所有其他操作将保持默认设置。让它 500MB 或更多,450MB 可能还不够。
  • 哦,我刚醒来 :) 也许这就是为什么我没有注意到“仅暂时用于此查询”的原因。谢谢。
【解决方案2】:
SET search_path='tmp';
-- Generate some data ...
-- DROP table tmp.table_name ;
-- CREATE table tmp.table_name ( n INTEGER NOT NULL PRIMARY KEY);
-- INSERT INTO tmp.table_name(n) SELECT generate_series(1,1000);
-- DELETE FROM tmp.table_name WHERE random() < 0.05 ;

except查询等价于下面的NOT EXISTS形式,在这里生成不同的查询计划(但结果相同)(9.0.1beta的东西)

-- EXPLAIN ANALYZE
WITH q1 AS (
    SELECT 1+tn.n  AS n
    FROM table_name tn
    WHERE NOT EXISTS (
        SELECT * FROM table_name nx
        WHERE nx.n = tn.n+1
        )   
    )
SELECT q1.n
FROM q1
ORDER BY q1.n DESC;

(具有递归 CTE 的版本也可能 :-)

编辑:查询计划。全部用于 100K 记录,其中 0.2% 已删除

原始查询:

    ------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=36461.76..36711.20 rows=99778 width=4) (actual time=2682.600..2682.917 rows=222 loops=1)
   Sort Key: q1.n
   Sort Method:  quicksort  Memory: 22kB
   ->  Subquery Scan q1  (cost=24984.41..26979.97 rows=99778 width=4) (actual time=2003.047..2682.036 rows=222 loops=1)
         ->  SetOp Except  (cost=24984.41..25982.19 rows=99778 width=4) (actual time=2003.042..2681.389 rows=222 loops=1)
               ->  Sort  (cost=24984.41..25483.30 rows=199556 width=4) (actual time=2002.584..2368.963 rows=199556 loops=1)
                     Sort Key: "*SELECT* 1".n
                     Sort Method:  external merge  Disk: 3512kB
                     ->  Append  (cost=0.00..5026.57 rows=199556 width=4) (actual time=0.071..1452.838 rows=199556 loops=1)
                           ->  Subquery Scan "*SELECT* 1"  (cost=0.00..2638.01 rows=99778 width=4) (actual time=0.067..470.652 rows=99778 loops=1)
                                 ->  Seq Scan on table_name  (cost=0.00..1640.22 rows=99778 width=4) (actual time=0.063..178.365 rows=99778 loops=1)
                           ->  Subquery Scan "*SELECT* 2"  (cost=0.00..2388.56 rows=99778 width=4) (actual time=0.014..429.224 rows=99778 loops=1)
                                 ->  Seq Scan on table_name  (cost=0.00..1390.78 rows=99778 width=4) (actual time=0.011..143.320 rows=99778 loops=1)
 Total runtime: 2684.840 ms
(14 rows)

不存在带有 CTE 的版本:

----------------------------------------------------------------------------------------------------------------------
 Sort  (cost=6394.60..6394.60 rows=1 width=4) (actual time=699.190..699.498 rows=222 loops=1)
   Sort Key: q1.n
   Sort Method:  quicksort  Memory: 22kB
   CTE q1
     ->  Hash Anti Join  (cost=2980.01..6394.57 rows=1 width=4) (actual time=312.262..697.985 rows=222 loops=1)
           Hash Cond: ((tn.n + 1) = nx.n)
           ->  Seq Scan on table_name tn  (cost=0.00..1390.78 rows=99778 width=4) (actual time=0.013..143.210 rows=99778 loops=1)
           ->  Hash  (cost=1390.78..1390.78 rows=99778 width=4) (actual time=309.923..309.923 rows=99778 loops=1)
                 ->  Seq Scan on table_name nx  (cost=0.00..1390.78 rows=99778 width=4) (actual time=0.007..144.102 rows=99778 loops=1)
   ->  CTE Scan on q1  (cost=0.00..0.02 rows=1 width=4) (actual time=312.270..698.742 rows=222 loops=1)
 Total runtime: 700.040 ms
(11 rows)

不存在-没有 CTE 的版本

--------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=6394.58..6394.58 rows=1 width=4) (actual time=692.313..692.625 rows=222 loops=1)
   Sort Key: ((1 + tn.n))
   Sort Method:  quicksort  Memory: 22kB
   ->  Hash Anti Join  (cost=2980.01..6394.57 rows=1 width=4) (actual time=308.046..691.849 rows=222 loops=1)
         Hash Cond: ((tn.n + 1) = nx.n)
         ->  Seq Scan on table_name tn  (cost=0.00..1390.78 rows=99778 width=4) (actual time=0.014..142.781 rows=99778 loops=1)
         ->  Hash  (cost=1390.78..1390.78 rows=99778 width=4) (actual time=305.732..305.732 rows=99778 loops=1)
               ->  Seq Scan on table_name nx  (cost=0.00..1390.78 rows=99778 width=4) (actual time=0.007..143.783 rows=99778 loops=1)
 Total runtime: 693.139 ms
(9 rows)

我的结论是“不存在”版本会导致 postgres 产生更好的计划。

【讨论】:

  • 替换NOT EXISTS 的有趣想法。但为什么是 CTE?您可以在同一查询级别进行所有操作。我的意思是,CTE 很酷,但性能胜过风格。 :)
  • 因为我可以! (也因为它与原始的相似)但是不同的查询计划表明原始的可能是次优的。 (这也是涉及聚合的子查询的情况;不存在是我的标准解决方法之一)
  • 顺便说一句:我认为这篇文章应该重新标记岛屿和差距。
  • 避免内部循环中的排序可能会在扩大规模时获得更好的回报,IMO。
猜你喜欢
  • 2014-12-02
  • 1970-01-01
  • 1970-01-01
  • 2018-08-15
  • 1970-01-01
  • 2017-03-23
  • 2023-03-29
  • 2016-03-03
  • 1970-01-01
相关资源
最近更新 更多