在PostgreSQL 14数据库中,我有一个交易表,目前包含大约4亿个元素,下面是对它的查询:
SELECT "transactions"."id"
FROM "transactions"
WHERE ("wallet_id" = $1)
ORDER BY "transactions"."id" DESC
LIMIT 10
这工作正常,查询速度也很快。EXPLAIN ANALYZE
输出:
Limit (cost=84991.29..84991.31 rows=10 width=146) (actual time=1.518..1.519 rows=2 loops=1)
-> Sort (cost=84991.29..85056.88 rows=26235 width=146) (actual time=1.517..1.518 rows=2 loops=1)
Sort Key: id DESC
Sort Method: quicksort Memory: 25kB
-> Index Scan using transactions_wallet_id_index on transactions (cost=0.57..84424.36 rows=26235 width=146) (actual time=1.080..1.497 rows=2 loops=1)
Index Cond: (wallet_id = $1)
Planning Time: 0.850 ms
Execution Time: 1.682 ms
如果我在where中添加第二个钱包id,那么当两个钱包都有少量交易时(第一个钱包为2,第二个钱包为57),查询需要几分钟:
SELECT "transactions"."id"
FROM "transactions"
WHERE ("wallet_id" = $1 OR "wallet_id" = $2)
ORDER BY "transactions"."id" DESC
LIMIT 10
其分析:
Limit (cost=0.57..117334.73 rows=10 width=146) (actual time=3683.524..944867.724 rows=10 loops=1)
-> Index Scan Backward using transactions_pkey on transactions (cost=0.57..615664040.26 rows=52471 width=146) (actual time=3683.523..944867.715 rows=10 loops=1)
Filter: ((wallet_id = $1) OR (wallet_id = $2))
Rows Removed by Filter: 117937409
Planning Time: 0.810 ms
Execution Time: 944867.797 ms
经过几个小时的调查,问题似乎来自于将ORDER BY
与LIMIT
结合使用,实际上,如果我删除其中一个,查询运行速度会很快。如果至少有一个钱包有非小数量的交易,它也会运行得很快。id
是主键,wallet_id
上有一个索引。
这非常令人失望和沮丧。这是我第一次使用Postgres,查询规划器在这样一个简单的查询上做得如此糟糕,这一事实真的很难理解。
我很感激一些关于如何使所有情况下的查询更快的建议。
我现在尝试了几个小时的不同事情,包括在table上运行VACUUM
(以防万一)和ANALYZE
,但都无济于事。
3条答案
按热度按时间t9eec4r01#
如果您从查询规划器的Angular 来看这个问题,它在这里有两个重要的操作要做:
1.过滤数据
1.分类数据
对于1.,您创建的索引transactions_wallet_id_index更可取。对于2.,主键附带的索引更好(好吧,向后扫描是)。请注意,在过滤掉数据之后,最优查询有一个逻辑Sort操作,而
OR
的查询没有,它只是有一个限制。我做了一个200万的table来重现你的场景。
现在,如果我们选择一个非常小的钱包,比如说2和3,它看起来像你所期望的,一个位图或两个条件:
但在某些时候,我们看到了你所看到的,钱包3和4:
查询规划器只是认为对该集合进行排序比过滤该集合更昂贵。
对于这个查询(我知道),没有真正的解决方法,
OR
查询在统计数据方面很混乱,相信我,不仅仅是Postgres受到影响。您可以尝试
ANALYZE transactions
,但您的统计数据可能是最新的。扩展的统计数据在这里也帮不了你。您可能会调整一些性能设置(特别是work_mem
,可能还有random_page_cost
)来强制执行正确的计划,但您会遇到其他查询的混乱。一个可靠的优化方法是自己做。正如我所说,
OR
查询是有问题的。尝试为您的查询创建一个支持索引,我们可以尝试几个选项。这可能会让习惯使用SQL Server的人感到惊讶,但是你可以通过使用
IN
(在SQL Server中,IN
被翻译为一堆ORs
)来获得Postgres的不同计划:你会得到一个很好的:
你也可以尝试通过告诉“这些不是你要找的ID”(或者有一个索引)来扰乱计划者的头脑:
(note 0+id - 1*id,id+id也可以,任何使它看起来与现有索引/ pk不同的东西):
在某些情况下,您也可以将查询拆分为两个并将它们联合在一起:
(this对于更大的钱包可能不太好用)
你有很多选择,
IN
应该是最好的,但你可能想尝试所有这些,看看哪一个产生最好的结果。vltsax252#
为什么?
额外的过滤器使Postgres切换到不同的查询计划,结果证明效率极低。这种效果或多或少是偶然触发的。潜在的问题是Postgres严重低估了
WHERE
子句的选择性。它期望它可以按照请求的排序顺序遍历PK(transactions_pkey
)上的索引,并很快找到几行(LIMIT 10
)。事实证明,过滤器是非常有选择性的,Postgres必须跳过超过1.18亿行(!!!)才能找到足够的匹配(Rows Removed by Filter: 117937409
)。如果没有足够的行来满足限制,那么在Postgres最终给予之前,必须访问所有行。最坏的情况。该决定是基于错误或误导性列统计数据作出的。如果您可以改进列统计信息,那么可能会自行解决这个问题。* 可能 * 像
ANALYZE transactions;
一样简单。有各种各样的方法。以下是昨天dba.SE上的一个相关回答,还有更多关于这个问题的内容(以及更多类似案例的链接):碰巧的是,我用
ORDER BY id + 0
讨论了同样的“蛮力”破解,你找到了你的答案。解决方案
对于您的特殊情况,也有不同的方法。要获得最佳性能,请创建多列索引(一次):
也大大提高了简单查询的性能。(即使这已经相当快了。)您将看到一个没有“排序”步骤的仅索引扫描(或至少是索引扫描)。
然后使用此查询:
所有括号均为必填项。
现在,第二个查询的成本大约是第一个查询的两倍,即非常快
sauutmhj3#
经过进一步的挖掘,我终于自己解决了这个问题,这要感谢this blog post。它只适用于
ORDER BY
列是整数(或者可能只是一个数字),通过将+ 0
添加到它。把它贴出来以防它能帮到别人。我还发现了几个报告给PostgreSQL的问题,这些问题与这个问题类似或相关,我发现一些核心开发人员对一些现实世界的使用问题的漠视令人难以置信。显然,他们找到了一个可以接受的设计决策,即通过在整数字段上将
+ 0
添加到ORDER BY
,在0.3秒内运行的查询可能需要20分钟,否则由于规划器中的一些早期中止功能出错。这让我认真地重新考虑了我们迁移到Postgres的决定,我们已经在考虑替代方案。真的很伤心