在EXISTS条件下创建索引

时间:2018-09-14 04:13:43

标签: postgresql indexing query-performance

我的表结构:

table_a(id, company_id, approval_status, is_locked)
table_b(tba_id, status)

我的查询

SELECT COUNT(id) filter (WHERE approval_status = 2 
AND is_locked = true AND EXISTS 
(SELECT 1 from table_b WHERE table_b.tba_id = table_a.id 
AND table_b.status = 2) 
FROM table_a
GROUP BY company_id

我目前有以下索引,但是性能仍然很慢:

CREATE INDEX multiple_filter_index ON table_a (approval_status, is_locked)

可以通过添加更好的索引来提高此查询的性能吗?

这是查询计划:

HashAggregate  (cost=463013.07..463013.10 rows=2 width=11) (actual time=47632.476..47632.476 rows=2 loops=1)
  Group Key: table_a.company_id
  ->  Seq Scan on table_a  (cost=0.00..3064.62 rows=100062 width=11) (actual time=0.003..23.326 rows=100062 loops=1)
  SubPlan 1
    ->  Seq Scan on table_b  (cost=0.00..477.27 rows=104 width=0) (actual time=1.430..1.430 rows=0 loops=33144)
          Filter: ((tba_id = table_a.id) AND (status = 2))
          Rows Removed by Filter: 17411
  SubPlan 2
    ->  Seq Scan on table_b table_b_1  (cost=0.00..433.73 rows=5820 width=4) (never executed)
          Filter: (status = 2)
Planning time: 0.902 ms
Execution time: 47632.565 ms

2 个答案:

答案 0 :(得分:1)

您当前的执行计划表明Postgres根本没有使用您定义的索引。相反,它只是对每个表进行两次连续扫描,如果这些表很大,则效率不高。

首先,AFAIK将按照以下方式执行查询:

SELECT COUNT(id)
FROM table_a
WHERE
    approval_status = 2 AND
    is_locked = true AND
    EXISTS (SELECT 1 from table_b WHERE table_b.tba_id = table_a.id AND table_b.status = 2)
GROUP BY company_id;

也就是说,Postgres过滤器的行为实际上与该逻辑在正式的WHERE子句中相同。

我建议在两个表的每个上创建索引:

CREATE INDEX table_a_idx ON table_a (approval_status, is_locked, company_id);
CREATE INDEX table_b_idx ON table_b (status, tba_id);

使用table_a_idx索引的原因是我们希望使用approval_statusis_locked过滤器来消除尽可能多的记录。我还在索引中包括了company_id,以涵盖GROUP BY列,希望避免在遍历索引后需要进行额外的磁盘读取。

table_b_idx的存在是为了加快查询的EXISTS子句。

我还建议您使用COUNT(*)而不是COUNT(id)

答案 1 :(得分:0)

尝试将一些过滤逻辑转移到联接中

SELECT
    company_id
  , COUNT(CASE
        WHEN approval_status = 2 AND
            is_locked = TRUE AND
            b.tba_id IS NOT NULL
        THEN id
    END)
FROM table_a
LEFT JOIN (
    SELECT DISTINCT tba_id 
    FROM table_b
    ) b on b.tba_id = table_a.id
GROUP BY
    company_id