Моя структура таблицы:

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
3
Tran Hung 14 Сен 2018 в 07:13

2 ответа

Лучший ответ

Ваш текущий план выполнения показывает, что 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_status и is_locked. Я также включил company_id в этот индекс, чтобы покрыть столбец GROUP BY, надеясь избежать необходимости выполнять дополнительное чтение с диска после обхода индекса.

table_b_idx существует для ускорения предложения EXISTS вашего запроса.

Я также рекомендую вам использовать COUNT(*) вместо COUNT(id).

1
Tim Biegeleisen 14 Сен 2018 в 05:10

Попробуйте переместить логику фильтрации в соединение

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
0
Used_By_Already 14 Сен 2018 в 04:52