我有这个查询(在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
上有btree索引,以及join_table.table_1_id
和join_table.table_2_id
当我只从查询中删除LIMIT 1
时,执行时间下降到~13ms。此特定查询当前仅返回一个结果(没有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 DESC
和LIMIT 1
(完全为1)
答案 0 :(得分:8)
好的,这是一个非常经典的案例。
每当您使用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
子句相同的表,那将会很棒:如果您很幸运,您可能在{{1}中有一列}与join_table
具有相同的顺序,因此它不会对您订购的产品产生任何影响。但是,要小心,这很容易出错(例如,序列填充的序号可能有大纲)。答案 1 :(得分:0)
虽然您只是添加限制1,但对查询的任何更改都会影响其执行计划和使用的索引。
要解决您的问题,因为您说当订单是ASC时您的查询效果良好:
似乎在table_1.created_at上创建的索引是ASC。 我知道在db2中,您可以指定何时创建索引为双向ASC / DESC。我想在postgresql你应该有相同的,如果不是你可以在同一个字段1上创建2个索引与排序DESC和另一个与SORT ASC