Postgres 中的慢查询优化

6

我们遇到了一个特定的 SQL 查询性能问题,正在尝试找出如何改进。它的执行时间约为20-100秒!

以下是查询语句及其解释:

SELECT  "jobs".* FROM "jobs"
  WHERE "jobs"."status" IN (1, 2, 3, 4)
  ORDER BY "jobs"."due_date" ASC
  LIMIT 5;


Limit  (cost=0.42..1844.98 rows=5 width=2642) (actual time=16927.150..18151.643 rows=1 loops=1)
   ->  Index Scan using index_jobs_on_due_date on jobs  (cost=0.42..1278647.41 rows=3466 width=2642) (actual time=16927.148..18151.641 rows=1 loops=1)
         Filter: (status = ANY ('{1,2,3,4}'::integer[]))
         Rows Removed by Filter: 595627
 Planning time: 0.205 ms
 Execution time: 18151.684 ms

我们正在AWS RDS上使用PostgreSQL 9.6.11。

在一张表中,我们有大约500K行数据。与查询相关的字段如下:

  • due_date(时间戳,没有时区,可以为null)
  • status(整数,非空)

我们拥有以下索引:

CREATE INDEX index_jobs_on_due_date ON public.jobs USING btree (due_date)
CREATE INDEX index_jobs_on_due_date_and_status ON public.jobs USING btree (due_date, status)
CREATE INDEX index_jobs_on_status ON public.jobs USING btree (status)
CREATE UNIQUE INDEX jobs_pkey ON public.jobs USING btree (id)

非常感谢你提前帮忙, - 杰克

1个回答

1
对于这个查询:
SELECT  j.*
FROM "jobs" j
WHERE j."status" IN (1, 2, 3, 4)
ORDER BY "jobs"."due_date" ASC
LIMIT 5;

“显而易见”的索引是在(status)上。但这可能没有帮助。目标是摆脱排序。因此,您可以重写查询并使用索引jobs(status, due_date)。”
select j.*
from ((select j.*
       from jobs j
       where j.status = 1
       order by j.due_date asc
       limit 5
      ) union all
      (select j.*
       from jobs j
       where j.status = 2
       order by j.due_date asc
       limit 5
      ) union all
      (select j.*
       from jobs j
       where j.status = 3
       order by j.due_date asc
       limit 5
      ) union all
      (select j.*
       from jobs j
       where j.status = 4
       order by j.due_date asc
       limit 5
      )
     ) j
order by due_date
limit 5;

每个子查询都应使用复合索引。最后的排序将在(最多)20行上进行,这应该很快。

编辑:

以下是一个相关的想法,使用相同的索引:

SELECT j.*
FROM (SELECT  j.*,
              ROW_NUMBER() OVER (PARTITION BY j.status ORDER BY j.due_date ASC) as seqnum
      FROM "jobs" j
     ) j
WHERE j.status in (1, 2, 3, 4) AND seqnum <= 5
ORDER BY j.due_date ASC
LIMIT 5;

这可以使用索引进行ROW_NUMBER()计算。但是,这可能需要对整个表进行全表扫描。但是,最终的排序将仅限于20行,因此最终的排序被消除了。


您提供的解决方案是有效的,但在我们的情况下,我们有很多类似的查询(从多个状态中选择)。此外,这些查询通常具有许多其他过滤器和条件。因此,构建这种联合将非常棘手。是否有一种方法可以强制在那里使用状态索引 - 看起来它很慢,因为它没有被使用。或者其他可能的解决方案?谢谢! - Jack Juiceson
@JackJuiceson……这不是强制使用“status”索引,而是关于之后的排序。 - Gordon Linoff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接