添加子查询时,PostgreSQL 查询速度非常慢

P.P*_*ter 13 postgresql performance subquery hibernate query-performance

我对一个有 1.5M 行的表有一个相对简单的查询:

SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;
Run Code Online (Sandbox Code Playgroud)

EXPLAIN ANALYZE 输出:

Limit  (cost=8.84..12.86 rows=1 width=8) (actual time=0.985..0.986 rows=1 loops=1)
  ->  Bitmap Heap Scan on publication  (cost=8.84..12.86 rows=1 width=8) (actual time=0.984..0.985 rows=1 loops=1)
        Recheck Cond: ((mtid = 9762715) OR (last_modifier = 21321))
        ->  BitmapOr  (cost=8.84..8.84 rows=1 width=0) (actual time=0.971..0.971 rows=0 loops=1)
              ->  Bitmap Index Scan on publication_pkey  (cost=0.00..4.42 rows=1 width=0) (actual time=0.295..0.295 rows=1 loops=1)
                    Index Cond: (mtid = 9762715)
              ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..4.42 rows=1 width=0) (actual time=0.674..0.674 rows=0 loops=1)
                    Index Cond: (last_modifier = 21321)
Total runtime: 1.027 ms
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都很好,速度很快,并且使用了可用的索引。
现在,如果我稍微修改一个查询,结果将是:

SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;
Run Code Online (Sandbox Code Playgroud)

EXPLAIN ANALYZE输出是:

Limit  (cost=0.01..2347.74 rows=5000 width=8) (actual time=2735.891..2841.398 rows=1 loops=1)
  ->  Seq Scan on publication  (cost=0.01..349652.84 rows=744661 width=8) (actual time=2735.888..2841.393 rows=1 loops=1)
        Filter: ((hashed SubPlan 1) OR (last_modifier = 21321))
        SubPlan 1
          ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
Total runtime: 2841.442 ms
Run Code Online (Sandbox Code Playgroud)

没那么快,而且使用 seq 扫描...

当然,应用程序运行的原始查询要复杂一些,甚至更慢,当然 hibernate 生成的原始查询不是(SELECT 9762715),但即使如此也很慢(SELECT 9762715)!查询是由休眠生成的,因此更改它们是一个很大的挑战,并且某些功能不可用(例如UNION不可用,这会很快)。

问题

  1. 为什么不能在第二种情况下使用索引?他们怎么可能被利用?
  2. 我可以通过其他方式提高查询性能吗?

其他想法

似乎我们可以通过手动执行 SELECT 来使用第一种情况,然后将结果列表放入查询中。即使 IN() 列表中有 5000 个数字,它也比第二种解决方案快四倍。然而,它似乎是错误的(而且,它可能快 100 倍:))。完全无法理解为什么查询规划器对这两个查询使用了完全不同的方法,所以我想找到一个更好的解决方案来解决这个问题。

Erw*_*ter 8

问题的核心在这里变得显而易见:

Seq Scan on Publication (cost=0.01..349652.84 rows=744661 width=8) (实际时间=2735.888..2841.393 rows=1 loops=1)

Postgres估计返回 744661 行,而事实上,结果是单行。如果 Postgres 不知道从查询中期望得到什么,它就无法更好地计划。我们需要看到隐藏在背后的实际查询(SELECT 9762715)——并且可能还需要知道表定义、约束、基数和数据分布。显然,Postgres的是无法预测如何行会被它返回。可能有重写查询的方法,具体取决于它什么。

如果您知道子查询永远不能返回多n行,您可以使用以下命令告诉 Postgres:

SELECT mtid
FROM   publication
WHERE  mtid IN (SELECT ... LIMIT n) --  OR last_modifier=21321
LIMIT  5000;
Run Code Online (Sandbox Code Playgroud)

如果n足够小,Postgres 将切换到(位图)索引扫描。但是,这只适用于简单的情况。添加OR条件时停止工作:查询计划器当前无法处理。

我很少使用IN (SELECT ...)开始。通常有更好的方法来实现相同的,通常是EXISTS半连接。有时用 ( LEFT) JOIN( LATERAL) ...

显而易见的解决方法是使用UNION,但您排除了这种情况。在不知道实际子查询和其他相关细节的情况下,我不能说更多。

  • _没有隐藏的查询_`(SELECT 9762715)`!如果我运行您在上面看到的确切查询。当然,最初的 hibernate 查询有点复杂,但我(认为我)设法查明查询计划器误入歧途的地方,所以我提出了查询的那部分。但是,上面的解释和查询是逐字的 ctrl-cv。 (2认同)

P.P*_*ter 8

我的同事找到了一种更改查询的方法,以便它需要简单的重写并执行它需要执行的操作,即在一个步骤中执行子选择,然后对结果执行进一步的操作:

SELECT mtid FROM publication 
WHERE 
  mtid = ANY( (SELECT ARRAY(SELECT 9762715))::bigint[] )
  OR last_modifier=21321
LIMIT 5000;
Run Code Online (Sandbox Code Playgroud)

现在的解释分析是:

 Limit  (cost=92.58..9442.38 rows=2478 width=8) (actual time=0.071..0.074 rows=1 loops=1)
   InitPlan 2 (returns $1)
     ->  Result  (cost=0.01..0.02 rows=1 width=0) (actual time=0.010..0.011 rows=1 loops=1)
           InitPlan 1 (returns $0)
             ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.002 rows=1 loops=1)
   ->  Bitmap Heap Scan on publication  (cost=92.56..9442.36 rows=2478 width=8) (actual time=0.069..0.070 rows=1 loops=1)
         Recheck Cond: ((mtid = ANY (($1)::bigint[])) OR (last_modifier = 21321))
         Heap Blocks: exact=1
         ->  BitmapOr  (cost=92.56..92.56 rows=2478 width=0) (actual time=0.060..0.060 rows=0 loops=1)
               ->  Bitmap Index Scan on publication_pkey  (cost=0.00..44.38 rows=10 width=0) (actual time=0.046..0.046 rows=1 loops=1)
                     Index Cond: (mtid = ANY (($1)::bigint[]))
               ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..46.94 rows=2468 width=0) (actual time=0.011..0.011 rows=0 loops=1)
                     Index Cond: (last_modifier = 21321)
 Planning time: 0.704 ms
 Execution time: 0.153 ms
Run Code Online (Sandbox Code Playgroud)

似乎我们可以创建一个简单的解析器,以这种方式查找和重写所有子选择,并将其添加到休眠钩子以操作本机查询。