postgresql 使用Postgres遇到限制[小值]问题

gcxthw6b  于 2023-06-29  发布在  PostgreSQL
关注(0)|答案(2)|浏览(163)

后台

当在一个相对简单的查询上使用小的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);

xkrw2x1b

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声明为正在实体化。

with r as materialized (
  ....

当然,这只是为了解释,因为拥有多列索引是一个简单而强大的解决方案。通过给你两全其美(同时在userid上选择和在created上排序),它消除了对哪个更重要做出令人担忧的决定的需要,就像当你有两个单列索引时规划器需要做的那样。

cigdeys3

cigdeys32#

核心问题是对索引扫描的错误估计:估计21339行,实际上只有一行。ANALYZE表,看看这是否足以提高估计。如果不是,则增加该列的统计信息:

ALTER TABLE rounds ALTER userid SET STATISTICS 500;
ANALYZE rounds;

如果你想为查询提供完美的索引,这个问题也应该消失:

CREATE INDEX ON rounds (userid, created);

然后你可以删除索引'rounds_userid_idx',因为新索引可以做该索引可以做的一切(除非它是唯一索引)。

相关问题