后台
当在一个相对简单的查询上使用小的LIMIT子句值时,我得到的查询非常慢。
我已经读了一遍又一遍PostgreSQL query very slow with limit 1。(并不是说我不可能错过任何东西,但它是相关的,人们不应该只是引导我那里没有给我一个线索,我可能会错过第一)。
我肯定遇到了这里提到的bug:对于较小的limit值,此查询需要大约7,409,626与53ns。
简单地将LIMIT从1更改为1000就可以获得瞬时速度,将其降至10或1可以获得 *OMGBBQ这里有什么问题 * 速度。
我试着从上面链接的SO中应用基本建议:在查询中添加一个无用的ORDERBY列来欺骗计划器。
然而,在我的例子中,即使我尝试将没有限制的主查询放在WITH子句中,规划器也非常慢!
查询
select id
from rounds
where userid = (
select id
from users
where integrationuserid = 'sample:64ce5bad-8c48-44a4-b473-5a7451980bb2')
order by created desc
limit 1;
部分解释分析结果:
Limit = 1的朴素查询
explain analyze select id from rounds where userid = (select id from users where integrationuserid = 'sample:64ce5bad-8c48-44a4-b473-5a7451980bb2') order by created desc, userid limit 1;
QUERY PLAN
------------------------------------------------
Limit (cost=3.07..47.03 rows=1 width=40) (actual time=7408.097..7408.099 rows=1 loops=1)
InitPlan 1 (returns $0)
-> Index Scan using users_integrationuserid_idx on users (cost=0.41..2.63 rows=1 width=16) (actual time=0.013..0.014 rows=1 loops=1)
Index Cond: (integrationuserid = 'sample:64ce5bad-8c48-44a4-b473-5a7451980bb2'::text)
-> Index Scan using recent_rounds_idx on rounds (cost=0.44..938182.73 rows=21339 width=40) (actual time=7408.096..7408.096 rows=1 loops=1)
Filter: (userid = $0)
Rows Removed by Filter: 23123821
Planning Time: 0.133 ms
Execution Time: 7408.114 ms
(9 rows)
vs. Limit = 1000(任意,只是为了看看会发生什么)
explain analyze select id from rounds where userid = (select id from users where integrationuserid = 'sample:64ce5bad-8c48-44a4-b473-5a7451980bb2') order by created desc, userid limit 1000;
QUERY PLAN
------------------------------------------------
Limit (cost=24163.47..24165.97 rows=1000 width=40) (actual time=0.048..0.049 rows=1 loops=1)
InitPlan 1 (returns $0)
-> Index Scan using users_integrationuserid_idx on users (cost=0.41..2.63 rows=1 width=16) (actual time=0.018..0.019 rows=1 loops=1)
Index Cond: (integrationuserid = 'sample:64ce5bad-8c48-44a4-b473-5a7451980bb2'::text)
-> Sort (cost=24160.84..24214.18 rows=21339 width=40) (actual time=0.047..0.048 rows=1 loops=1)
Sort Key: rounds.created DESC
Sort Method: quicksort Memory: 25kB
-> Bitmap Heap Scan on rounds (cost=226.44..22990.84 rows=21339 width=40) (actual time=0.043..0.043 rows=1 loops=1)
Recheck Cond: (userid = $0)
Heap Blocks: exact=1
-> Bitmap Index Scan on rounds_userid_idx (cost=0.00..221.10 rows=21339 width=0) (actual time=0.040..0.040 rows=1 loops=1)
Index Cond: (userid = $0)
Planning Time: 0.108 ms
Execution Time: 0.068 ms
(14 rows)
我的基本问题是:
1.为什么一开始就这么糟糕(在应用where子句后,扫描整个数据库中的所有行会比扫描一个较小的子集好得多)?
1.我该如何解决这个问题?
我需要规划器将原始表减少到只匹配WHERE子句的行-然后对其应用sort & limit。
但它却将排序和限制应用于整个表-在本例中-大约2300万个项目-并产生了令人惊讶的可怕结果。
我已经尝试了许多语法,试图创建一个子查询,首先提取用户的轮次,然后尝试应用限制。但同样,计划者看穿了这一点,并将限制应用于最初的2300万件商品。
其他尝试/信息
这篇文章指出,原来的答案(我的初始链接,上面)不再工作,因为第13页-并使用CTE。
设置LIMIT时Postgres SLOWER:除了添加一个虚拟的ORDER BY
之外,如何修复?
然而,这基本上是我的第一直觉-所有CTE的使用对我来说都失败了。
一次CTE尝试:(非常慢!)
with r as (
select id, created
from rounds
where userid = (
select id
from users
where integrationuserid = 'sample:64ce5bad-8c48-44a4-b473-5a7451980bb2')
)
select r.id from r order by r.created desc limit 1;
也许做随机的东西与移动顺序和限制周围的帮助?(不!)
用r as(select id,created from rounds where userid =(select id from users where integrationuserid = 'sample:64ce5bad-8c48-44a4-b473- 5a7451980bb2')order by created desc)select r.id from r limit 1;
解决方案(感谢@jjanes为我指出它,并感谢@denis-de-bernardy首先分享它):解决方案,顺便说一下:
创建索引recent_rounds_idx on rounds(userid,created desc);
2条答案
按热度按时间xkrw2x1b1#
为什么一开始就这么糟糕(在应用where子句后,扫描整个数据库中的所有行会比扫描一个较小的子集好得多)?
在宇宙中它认为那不会发生。通过遍历已经按所需顺序排列的行的索引,它认为可以在找到满足所需userid的第一行后停止,这意味着它认为可以在阅读表的1/21340后停止。这显然是非常错误的,因为只有1行满足该条件,而不是像它认为的那样有21339行。
那么一个附带的问题,为什么估计是如此错误?由于它在这里进行了一般性的估计(在计划时,它不知道$0的值会是多少),它猜测合格的行数将是表中估计的行数,除以userid的n_distinct的估计值(针对空值进行了调整,但为了简单起见,我将忽略它)。因此,要么这是正确的,并且有一些userid值有很多行(但它们恰好不是您正在寻找的值),这些值会拉高平均值。
或者它是不正确的,对n_distinct的估计与现实相差甚远。从远处我们无法得知。您可以通过查看pg_stats.n_distinct来查看估计值,并将其与轮次中的count(distinct userid)的真实值进行比较。如果估计值相差很远,您可以通过执行
alter table rounds alter column userid set (N_DISTINCT = <true value>)
手动修复它。您需要在此生效之前分析该表。严重错误的n_distinct问题是由于用于计算统计数据的样本选择不足(每行都有可能被选择为样本,但每行的选择并不严格独立于其他行),并且Laurenz建议的增加STATISTICS只能微弱地克服这个问题。而设置N_DISTINCT则直接处理它。一次CTE尝试:(非常慢!)
你的尝试非常接近。但是现代PostgreSQL可以“看穿”CTE,并像计划原始查询一样计划它。为了防止这种情况,您需要将CTE声明为正在实体化。
当然,这只是为了解释,因为拥有多列索引是一个简单而强大的解决方案。通过给你两全其美(同时在userid上选择和在created上排序),它消除了对哪个更重要做出令人担忧的决定的需要,就像当你有两个单列索引时规划器需要做的那样。
cigdeys32#
核心问题是对索引扫描的错误估计:估计21339行,实际上只有一行。
ANALYZE
表,看看这是否足以提高估计。如果不是,则增加该列的统计信息:如果你想为查询提供完美的索引,这个问题也应该消失:
然后你可以删除索引'rounds_userid_idx',因为新索引可以做该索引可以做的一切(除非它是唯一索引)。