【问题标题】:Query with ORDER BY is 13 times as slow when I add LIMIT 1当我添加 LIMIT 1 时,使用 ORDER BY 的查询速度是 13 倍
【发布时间】:2015-07-31 19:18:01
【问题描述】:

我有这个查询(在 postgresql 中):

SELECT "table_1".* FROM "table_1"
INNER JOIN "join_table"
  ON "table_1"."id" = "join_table"."table_1_id"
WHERE "join_table"."table_2_id" = 650727
ORDER BY table_1.created_at DESC
LIMIT 1

返回 1 个结果,但执行时间约为 250-300 毫秒

table_1.created_at,还有join_table.table_1_idjoin_table.table_2_id上有btree索引

当我只从查询中删除 LIMIT 1 时,执行时间下降到 ~13 毫秒。此特定查询当前仅返回一个结果(没有 LIMIT),但在 WHERE 中还有其他具有不同值的结果可能返回更多(这就是需要 LIMIT 的原因)。

为什么向已经只返回单个结果的查询添加 LIMIT 会大大增加执行时间?

这是LIMIT 1 的解释计划(这些对我来说总是很难完全理解...):http://explain.depesz.com/s/rOy

这里是没有 LIMIT 1 的解释计划:http://explain.depesz.com/s/q3d7

此外,如果我保留LIMIT 1,但将顺序更改为ASC,则查询也会下降到 13 毫秒。如果我将LIMIT 更改为LIMIT 20(但保留ORDER BY DESC)只需要22ms...wtf!?

所以它与ORDER BY DESCLIMIT 1的组合有关(正好1)

【问题讨论】:

  • 听起来你可能在你排序的列上有索引,因此当你的订单与索引匹配时它很快,当它与索引相反时它必须在它之前的内存中排序能够呈现结果。你能列出你的表上有哪些索引吗?
  • 您是否尝试查看在 stackoverflow 上对 postgres 有限制的查询的其他性能问题?有很多主题,也许这些有帮助。
  • 您是否有 3 个索引或 2 个索引,其中一个“复合索引”包含“join_table.table_1_id”以及“join_table.table_2_id”?使用复合索引,连接过滤可以完全由该索引处理。例如:在join_table(table_2_id,table_1_id)上创建索引join_table_ix1;

标签: sql postgresql query-optimization sql-execution-plan


【解决方案1】:

好的,这是一个非常经典的案例。

每当您使用LIMIT(或类似FETCH FIRST ... ROWS ONLY)时,优化器都会尝试优化查询,以便尽可能快地仅获取第一行。这意味着优化器偏好第一个成本值较低的执行计划,而不是执行计划中显示的第二个。请记住:PostgreSQL 显示的两个成本值(例如,cost=48.150..6,416.240 是设置成本 (48.150) 和总执行成本 (6,416.240)。

这里的“问题”是您有一个支持您的ORDER BY 子句的索引。因此,PostgreSQL 认为它可以通过这个索引(由于查询中的 DESC 修饰符而以相反的顺序)并检查另一个表中的每一行是否满足另一个 WHERE 子句。问题是优化器无法知道这是第一行还是最后一行(根据ORDER BY)。优化器进行任意猜测,并认为匹配的行将更接近开始而不是结束。然后使用这个乐观的估计来计算成本值,结果过于乐观,以至于 PostgreSQL 最终确定了一个糟糕的执行计划。

当您将 ORDER BY ... DESC 更改为 ORDER BY ... ASC 时,优化器会执行相同的任意但乐观的估计,结果证明在这种情况下更正确,因此您可以获得更好的执行时间。

但是,从优化的角度来看,根本原因是优化器估计有 2,491 行将匹配 WHERE 子句 tango = 650727。当优化器正确估计这只是命中几行时,问题可能不会发生。

WHERE 子句非常简单,因此良好的估计应该没有问题。那么,主要问题是:您对该表的统计数据如何?

有几种方法可以解决这个问题:

  • 更新您的统计信息 (ANALYZE) 看看是否有帮助。
  • 增加为该列存储的最常用值的数量 (ALTER TABLE ... SET STATISTICS)。这也增加了用于收集统计数据的样本量,这意味着 ANALYZE 需要更长的时间,但会产生更准确的结果。

理论上,这应该足以解决该问题。但是,其他选项是:

  • 如果您出于其他原因(如其他查询)不需要 created_at 上的索引,请删除它。
  • 重新编写查询,使错误的执行计划不再是选项。特别是,如果您可以编写查询以使ORDER BY 子句使用与WHERE 子句相同的表,那就太好了:如果幸运的话,您可能在join_table 中有一个具有相同顺序的列为table_1.created_at,这样您订购的商品不会有任何区别。但是,请注意,这很容易出错(例如,由序列填充的序列号可能有轮廓线)。

【讨论】:

    【解决方案2】:

    虽然您只添加限制 1,但对查询的任何更改都会影响其执行计划和使用的索引。

    为了解决您的问题,因为您说当订单为 ASC 时,您的查询性能良好:

    table_1.created_at 上创建的索引似乎是 ASC。 我知道在 db2 中,您可以在创建索引时指定为双向 ASC/DESC。我想在 postgresql 中你应该有相同的,如果没有,你可以在同一个字段 1 上使用 sort DESC 创建 2 个索引,另一个使用 SORT ASC

    【讨论】:

      猜你喜欢
      • 2013-02-11
      • 2023-03-21
      • 2015-11-23
      • 2020-11-19
      • 2019-05-09
      • 1970-01-01
      • 2012-10-07
      • 2013-09-20
      • 1970-01-01
      相关资源
      最近更新 更多