简体   繁体   English

如何修复任何($1)数组参数的 PostgreSQL 通用计划估计?

[英]How to fix PostgreSQL generic plan estimate for any($1) array parameter?

I've run into an issue where PostgreSQL (13.1 on windows, default config) would start preferring a generic plan after 5 executions because the plan estimate is clearly wrong.我遇到了一个问题,PostgreSQL(windows 上的 13.1,默认配置)会在 5 次执行后开始首选通用计划,因为计划估计显然是错误的。 Thing is, the custom plan is up to 10 times faster than the generic plan, and at least 2.5 times faster in the "worst" case.问题是,自定义计划比通用计划快 10 倍,在“最坏”情况下至少快 2.5 倍。 I don't want to use set plan_cache_mode = force_custom_plan, because the query is part of a software run in parallel with connection pooling over several threads, as well as dozens of smaller and larger queries, and it would feel like doing a hacky workaround that might also be dangerous for other queries.我不想使用 set plan_cache_mode = force_custom_plan,因为查询是与多个线程上的连接池并行运行的软件的一部分,以及几十个更小和更大的查询,感觉就像是在做一个 hacky 解决方法对于其他查询也可能很危险。

I think I've tracked it down to the generic plan estimate being wrong for the x.id = ANY($1) parts in the where clause which I'Ve got three of.我想我已经把它追溯到通用计划估计错误的 where 子句中的 x.id = ANY($1) 部分,我已经得到了三个。

Query goes like this:查询是这样的:

SELECT... FROM... WHERE... x.id = ANY($1) AND y.id = ANY($2) AND z.id = ANY($3)... SELECT...从...哪里... x.id = ANY($1) AND y.id = ANY($2) AND z.id = ANY($3)...

of course, this is hugely simplified, but I know that I pass an int8[] of 50 entries for the first parameter 99% of the time (because the query is for a paged view of records, where one page has 50 records), which is correctly estimated as 50 rows for the custom query plan, but 10 rows for the generic query plan.当然,这已经大大简化了,但我知道我在 99% 的时间内为第一个参数传递了 50 个条目的 int8[](因为查询是针对记录的分页视图,其中一页有 50 条记录),对于自定义查询计划,正确估计为 50 行,但对于通用查询计划,估计为 10 行。

Switching to using IN($1, $2, ... $50) fixes this, but we're currently trying to move over our IN clauses to ANY, because it's more efficient over JDBC and we've been bitten by the parameter limit a few times with IN (which doesn't happen with ANY, as then it's only one parameter).切换到使用 IN($1, $2, ... $50) 解决了这个问题,但我们目前正试图将我们的 IN 子句移至 ANY,因为它比 JDBC 更有效,而且我们已经被参数限制咬住了一些与 IN 的时间(这不会发生在任何情况下,因为它只是一个参数)。 Also, this would make the count of parameters variable, so the query planner would get a different query quite often (only $1 is pretty much always an array of 50 values on our prod system, the others may have less or more depending on lots of factors).此外,这会使参数的计数可变,因此查询规划器会经常得到不同的查询(在我们的产品系统上,只有 $1 几乎总是一个包含 50 个值的数组,其他的可能会更少或更多,这取决于很多因素)。

So far I've tried, without much success:到目前为止,我已经尝试过,但没有取得多大成功:

  • casting things to try to hint the planner铸造东西以试图暗示计划者
  • create statistics on various involved tables在各种涉及的表上创建统计信息
  • increase statistics target on involved columns增加所涉及列的统计目标
  • using unnest (this fixes the estimate, but then the query is about 20 times slower in any case, so not really a solution for me) and a few dozen other "hacky" workarounds like using regex split to table and the like.使用 unnest (这修复了估计,但是无论如何查询都会慢 20 倍,所以对我来说并不是真正的解决方案)和其他几十个“hacky”解决方法,比如使用 regex split to table 等。

Note that I do know why it uses a generic plan after the 5th execution - because the estimate of that is lower than the average estimate of the 5 previous custom plans.请注意,我确实知道它为什么在第 5 次执行后使用通用计划 - 因为该估计值低于 5 个先前自定义计划的平均估计值。 But I don't know how to fix that wrong estimate.但我不知道如何修正这个错误的估计。

Here's the relevant rows of the custom plan:这是自定义计划的相关行:

                                                                            ->  Bitmap Heap Scan on sbuilding b  (cost=150.51..332.59 rows=50 width=29) (actual time=0.077..0.187 rows=50 loops=2)
                                                                                Recheck Cond: (id = ANY ('{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50}'::bigint[]))
                                                                                Filter: (deletedat IS NULL)
                                                                                Heap Blocks: exact=50
                                                                                ->  Bitmap Index Scan on sbuilding_pkey  (cost=0.00..150.50 rows=50 width=0) (actual time=0.065..0.066 rows=50 loops=2)
                                                                                      Index Cond: (id = ANY ('{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50}'::bigint[]))

And the same part for the generic plan:通用计划的相同部分:

                                                                    ->  Index Scan using sbuilding_pkey on sbuilding b  (cost=0.28..82.84 rows=10 width=29) (actual time=0.049..0.229 rows=50 loops=2)
                                                                          Index Cond: (id = ANY ($2))
                                                                          Filter: (deletedat IS NULL)

I've replaced actual real ids with numbers 1-50, but the real ids all exist, as they are retrieved from the db in a step before this one.我已经用数字 1-50 替换了实际的真实 id,但是真实的 id 都存在,因为它们是在此之前的一步从数据库中检索的。

I'd prefer not having to post the full query or query plans, as those contain a lot of sensitive information (would be hundreds of words and values to replace for me), but I hope these parts are enough to show the issue and hopefully one of you will be able to assist.我不希望发布完整的查询或查询计划,因为它们包含很多敏感信息(对我来说将是数百个单词和值来替换),但我希望这些部分足以显示问题并希望你们中的一位将能够提供帮助。

Edit: I don't have any array extension like intArray installed explicitly - found a mailing list thread regarding estimates with intArray extension, so I thought I'd add this.编辑:我没有明确安装任何像 intArray 这样的数组扩展 - 找到了一个关于使用 intArray 扩展的估计的邮件列表线程,所以我想我会添加这个。 Not sure though if it still applies?不确定它是否仍然适用? Here's the link: https://www.postgresql.org/message-id/20150317191531.GE10492%40momjian.us这是链接: https://www.postgresql.org/message-id/20150317191531.GE10492%40momjian.us

Thanks a lot!非常感谢!

Edit2: I just retested with a super simple query and it seems that the query planner always assumes 10 values for = ANY(:arrayParam) for generic plans. Edit2:我刚刚用一个超级简单的查询重新测试,似乎查询计划器总是假设通用计划的 = ANY(:arrayParam) 有 10 个值。 This seems to be the culprit in my case.这似乎是我的罪魁祸首。 Is there any way to "fix" that assumption?有没有办法“修复”这个假设? Preferable without installing any extensions that need config changes like pg_hint_plan.最好不安装任何需要配置更改的扩展,例如 pg_hint_plan。 Thx!谢谢!

Edit3: found the following in postgres source code selfuncs.c: Edit3:在 postgres 源代码 selfuncs.c 中找到以下内容:

/*
         * Arbitrarily assume 10 elements in the eventual array value (see
         * also estimate_array_length).  We don't risk an assumption of
         * disjoint probabilities here.
         */
        for (i = 0; i < 10; i++)
        {
            if (useOr)
                s1 = s1 + s2 - s1 * s2;
            else
                s1 = s1 * s2;
        }

I'll see where I can get from there, but I still hope someone has a solution that does not involve me going through the whole postgres source code;)我会看看我能从哪里得到,但我仍然希望有人有一个不涉及我浏览整个 postgres 源代码的解决方案;)

That can be done by changing plan_cache_mode for this query:这可以通过更改此查询的plan_cache_mode来完成:

BEGIN;
SET LOCAL plan_cache_mode = force_custom_plan;
SETECT /* your query */
COMMIT;

That will make the optimizer always use a custom plan.这将使优化器始终使用自定义计划。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM