【问题标题】:Postgres min function performancePostgres min 函数性能
【发布时间】:2012-11-12 20:05:21
【问题描述】:

我需要runnerId 的最低值。

这个查询:

SELECT "runnerId" FROM betlog WHERE "marketId" = '107416794' ;

需要 80 毫秒(1968 行结果)。

这个:

SELECT min("runnerId") FROM betlog WHERE "marketId" = '107416794' ;

需要 1600 毫秒。

有没有更快的方法来找到最小值,或者我应该在我的 java 程序中计算最小值?

"Result  (cost=100.88..100.89 rows=1 width=0)"
"  InitPlan 1 (returns $0)"
"    ->  Limit  (cost=0.00..100.88 rows=1 width=9)"
"          ->  Index Scan using runneridindex on betlog  (cost=0.00..410066.33 rows=4065 width=9)"
"                Index Cond: ("runnerId" IS NOT NULL)"
"                Filter: ("marketId" = 107416794::bigint)"

CREATE INDEX marketidindex
  ON betlog
  USING btree
  ("marketId" COLLATE pg_catalog."default");

另一个想法:

SELECT "runnerId" FROM betlog WHERE "marketId" = '107416794' ORDER BY "runnerId" LIMIT 1 >1600ms
SELECT "runnerId" FROM betlog WHERE "marketId" = '107416794' ORDER BY "runnerId" >>100ms

LIMIT 如何降低查询速度?

【问题讨论】:

标签: sql performance postgresql indexing sql-limit


【解决方案1】:

你需要的是multi-column index:

CREATE INDEX betlog_mult_idx ON betlog ("marketId", "runnerId");

如果有兴趣,您可以在this related question on dba.SE 下找到有关 PostgreSQL 中的多列索引、链接和基准的深入信息。

我是怎么想到的?
在多列索引中,行按索引的第一列(“marketId”)排序(从而聚集),每个簇依次按索引的第二列排序 - 因此第一行符合条件min("runnerId")。这使得索引扫描非常快。

关于LIMIT 减慢查询速度的悖论效应——Postgres 查询规划器有一个弱点。常见的解决方法是使用 CTE(在这种情况下不是必要的)。在这个最近的密切相关的问题下查找更多信息:
PostgreSQL query taking too long

【讨论】:

  • 哇这解决了这个问题,你能提供一点背景为什么吗?你是怎么认出来的?
  • @wutzebaer:我添加了一个指向手册的链接,一个指向问题的链接,其中包含有关多列索引的更多信息和一些解释。
  • 这真的很奇怪——“坏”查询的解释是什么?处理 4065 行不应花费 1500 毫秒。
【解决方案2】:

PostgreSQL 将使用整个表的顺序扫描来执行 min 语句。您可以使用以下方法优化查询: SELECT col FROM sometable ORDER BY col ASC LIMIT 1;

【讨论】:

  • 只是订购也很快 >>SELECT "runnerId" FROM betlog WHERE "marketId" = '107416794' ORDER BY "runnerId"
  • 所以基本上你可以在没有限制声明的情况下使用order by approach。这应该会优化您的查询。
  • 好的,但是限制如何减慢查询速度?这是一个问题,因为我想将此查询用作子查询
  • 似乎 LIMIT 破坏了 PostgreSQL 中的查询计划器。
  • 这个答案不太正确,礼貌地说。 PostgreSQL 查询计划器可以并且将会为 min()max() 等聚合函数以及 ORDER BY ... LIMIT 1 使用匹配索引。
【解决方案3】:

当您在("runnerId") 上有索引(或至少将"runnerId" 作为高阶列)但在("marketId", "runnerId") 上没有索引时,它会将传递所有行的成本与匹配的"marketId" 进行比较使用该列上的索引并从该集合中挑选出最小的"runnerId" 以使用"runnerId" 上的索引进行扫描,并在找到与"marketId" 匹配的第一行时停止。基于可用的统计数据以及"marketId" 值将随机分布在"runnerId" 上索引的索引条目中的假设,它估计后一种方法的成本较低。

它还估计了扫描整个表并从匹配行中选择最小值以及可能的其他一些替代方案的成本。它并不总是使用某种类型的计划,而是比较所有备选方案的成本。

问题在于,值将随机分布在范围内的假设不一定正确(如本例所示),导致扫描范围的高百分比以查找潜伏在末尾的行。对于"marketId" 的某些值,其中所选值在"runnerId" 索引的开头附近可用,此计划应该非常快。

PostgreSQL 开发人员社区已经讨论过,如果数据分布不是假设的那样,我们可能会如何偏向那些“有风险”的计划,即如果数据分布不符合预期,并且已经开展了跟踪多列统计数据的工作这样相关值就不会遇到此类问题。预计在接下来的几个版本中会在这方面有所改进。在那之前,Erwin 的建议都是针对如何解决这个问题的。

基本上,它归结为提供更具吸引力的计划或引入优化障碍。在这种情况下,您可以通过在("marketId", "runnerId") 上添加索引来提供更有吸引力的选项——这允许以非常直接的方式直接获得答案。计划者为该备选方案分配了非常低的成本,从而使其被选中。如果您不想添加索引,则可以通过执行以下操作来强制设置优化障碍:

SELECT min("runnerId")
  FROM (SELECT "runnerId" FROM betlog
          WHERE "marketId" = '107416794'
          OFFSET 0) x;

当有OFFSET 子句(即使偏移量为零)时,它会强制单独计划子查询并将其结果提供给外部查询。我希望这将在 80 毫秒内运行,而不是在没有优化障碍的情况下运行的 1600 毫秒。当然,如果可以添加索引,缓存数据时的查询速度应该在1毫秒以内。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-15
    • 1970-01-01
    • 1970-01-01
    • 2012-05-28
    • 2017-06-05
    • 2011-08-16
    • 2016-03-22
    • 1970-01-01
    相关资源
    最近更新 更多