关于sql:Postgres中的慢查询优化

Slow query optimisation in Postgres

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

这是查询和解释:

1
2
3
4
5
6
7
8
9
10
11
12
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(没有时区的时间戳,可以为空)
  • 状态(整数,非空)

我们有以下索引:

1
2
3
4
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
2
3
4
5
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):

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
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 行上进行,这应该很快)。

编辑:

这是一个相关的想法,索引相同:

1
2
3
4
5
6
7
8
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 行,因此最终排序被淘汰。