2016-08-02 2 views
-1

РЕЗЮМЕ

Добавление определенных критериев в мульти-соединения таблиц с большим количеством строк заканчивается порядков медленнее запросов. Я пробовал много вещей, чтобы сделать это быстрее, включая все типы соединений в таблице, переупорядочение соединений, переупорядочение предложения WHERE, выполнение подзапросов, использование операторов CASE в предложении WHERE и т. Д.Postgres нескольких оптимизации соединения запросов с условиями на нескольких таблицах

Спецификации SQL ниже.

ВОПРОСЫ
  1. Почему добавление этого простого условия вызывают планировщик радикально изменить свой план выполнения?
  2. Можно ли сказать, планировщик, как анализировать конкретное состояние первых, не коренным образом изменить запрос или делать подзапросы (используя WITH, например)

Примечание: Я пытаюсь написать универсальный SQL строитель в течение API, позволяя вызывающим абонентам задавать произвольные условия в любой точке графика. Проблема в том, что некоторые из этих вызовов быстро вспыхивают, а другие не связаны с тем, как планы Postgres планируют исполнение. Оптимизация, созданная специально для этого запроса, не поможет мне удовлетворить более широкую задачу генератора SQL-запросов.

ДЕТАЛИ

У меня есть схема, которая хранит вершины и ребра (упрощенно базы данных граф) в Postgres:

CREATE TABLE IF NOT EXISTS vertex (type text, id serial, name text, data jsonb, UNIQUE (id)) 
CREATE INDEX vertex_data_idx ON vertex USING gin (data jsonb_path_ops) 
CREATE INDEX vertex_type_idx ON vertex (type) 
CREATE INDEX vertex_name_idx ON vertex (name) 
CREATE TABLE IF NOT EXISTS edge (src integer REFERENCES vertex (id), dst integer REFERENCES vertex (id)) 
CREATE INDEX edge_src_idx ON edge (src) 
CREATE INDEX edge_dst_idx ON edge (dst) 

В схемы хранятся графики, один из которых, как это: ПЛАНЕТА -> КОНТИНЕНТ -> СТРАНА -> РЕГИОН

есть 447554 общих вершин и 3155047 весь край в моей базе данных выборки, но данные, которые имеют отношение здесь:

  • 5 планеты (каждый относится к 5 континентам)
  • 25 материки (каждый относится к 2500 Countrys)
  • 62500 Countrys (25% из которых относятся к 100 РЕГИОНАМ каждым, остальные не имеют областей отношений)
  • 250000 РЕГИОНЫ

Этот запрос ищет планет, которые имеют испаноговорящих в любом регионе является быстро:

SELECT DISTINCT 
    v1.name as name, v1.id as id 
FROM vertex v1 
    LEFT JOIN edge e1 ON (v1.id = e1.src) 
    LEFT JOIN vertex v2 ON (v2.id = e1.dst) 
    LEFT JOIN edge e2 ON (v2.id = e2.src) 
    LEFT JOIN vertex v3 ON (v3.id = e2.dst) 
    LEFT JOIN edge e3 ON (v3.id = e3.src) 
    LEFT JOIN vertex v4 ON (v4.id = e3.dst) 
WHERE 
    v4.type = 'REGION' AND 
    v4.data @> '{"languages":["spanish"]}'::jsonb 

Планирование времени: 6.289 мс Время Исполнение: 0,744 мс

Когда я добавить условие на индексированного столбца в первой таблице в графе (v1), который не оказывает никакого влияния на результат, запрос является 12657 раз медленнее:

SELECT DISTINCT 
    v1.name as name, v1.id as id 
FROM vertex v1 
    LEFT JOIN edge e1 ON (v1.id = e1.src) 
    LEFT JOIN vertex v2 ON (v2.id = e1.dst) 
    LEFT JOIN edge e2 ON (v2.id = e2.src) 
    LEFT JOIN vertex v3 ON (v3.id = e2.dst) 
    LEFT JOIN edge e3 ON (v3.id = e3.src) 
    LEFT JOIN vertex v4 ON (v4.id = e3.dst) 
WHERE 
    v1.type = 'PLANET' AND 
    v4.type = 'REGION' AND 
    v4.data @> '{"languages":["spanish"]}'::jsonb 

Планирование времени: 7.664 мс время выполнения: 89010.096 мс

Это EXPLAIN (ПРОАНАЛИЗИРУЙТЕ, BUFFERS) на первом, быстрого вызова:

Unique (cost=154592.03..155453.96 rows=114925 width=28) (actual time=0.585..0.616 rows=4 loops=1) 
    Buffers: shared hit=92 
    -> Sort (cost=154592.03..154879.34 rows=114925 width=28) (actual time=0.579..0.588 rows=4 loops=1) 
     Sort Key: v1.name, v1.id 
     Sort Method: quicksort Memory: 17kB 
     Buffers: shared hit=92 
     -> Nested Loop (cost=37.96..142377.39 rows=114925 width=28) (actual time=0.155..0.549 rows=4 loops=1) 
       Buffers: shared hit=92 
       -> Nested Loop (cost=37.53..80131.76 rows=114925 width=4) (actual time=0.141..0.468 rows=4 loops=1) 
        Join Filter: (v2.id = e1.dst) 
        Buffers: shared hit=76 
        -> Nested Loop (cost=37.10..49179.08 rows=14270 width=8) (actual time=0.126..0.386 rows=4 loops=1) 
          Buffers: shared hit=60 
          -> Nested Loop (cost=36.68..41450.17 rows=14270 width=4) (actual time=0.112..0.304 rows=4 loops=1) 
           Join Filter: (v3.id = e2.dst) 
           Buffers: shared hit=44 
           -> Nested Loop (cost=36.25..37606.57 rows=1772 width=8) (actual time=0.092..0.209 rows=4 loops=1) 
             Buffers: shared hit=28 
             -> Nested Loop (cost=35.83..36646.82 rows=1772 width=4) (actual time=0.074..0.116 rows=4 loops=1) 
              Buffers: shared hit=12 
              -> Bitmap Heap Scan on vertex v4 (cost=30.99..1514.00 rows=220 width=4) (actual time=0.039..0.042 rows=1 loops=1) 
                Recheck Cond: (data @> '{"languages":["spanish"]}'::jsonb) 
                Filter: (type = 'REGION'::text) 
                Heap Blocks: exact=1 
                Buffers: shared hit=5 
                -> Bitmap Index Scan on vertex_data_idx (cost=0.00..30.94 rows=392 width=0) (actual time=0.020..0.020 rows=1 loops=1) 
                 Index Cond: (data @> '{"languages":["spanish"]}'::jsonb) 
                 Buffers: shared hit=4 
              -> Bitmap Heap Scan on edge e3 (cost=4.84..159.12 rows=57 width=8) (actual time=0.023..0.037 rows=4 loops=1) 
                Recheck Cond: (dst = v4.id) 
                Heap Blocks: exact=4 
                Buffers: shared hit=7 
                -> Bitmap Index Scan on edge_dst_idx (cost=0.00..4.82 rows=57 width=0) (actual time=0.013..0.013 rows=4 loops=1) 
                 Index Cond: (dst = v4.id) 
                 Buffers: shared hit=3 
             -> Index Only Scan using vertex_id_key on vertex v3 (cost=0.42..0.53 rows=1 width=4) (actual time=0.008..0.011 rows=1 loops=4) 
              Index Cond: (id = e3.src) 
              Heap Fetches: 4 
              Buffers: shared hit=16 
           -> Index Scan using edge_dst_idx on edge e2 (cost=0.43..1.46 rows=57 width=8) (actual time=0.008..0.011 rows=1 loops=4) 
             Index Cond: (dst = e3.src) 
             Buffers: shared hit=16 
          -> Index Only Scan using vertex_id_key on vertex v2 (cost=0.42..0.53 rows=1 width=4) (actual time=0.006..0.009 rows=1 loops=4) 
           Index Cond: (id = e2.src) 
           Heap Fetches: 4 
           Buffers: shared hit=16 
        -> Index Scan using edge_dst_idx on edge e1 (cost=0.43..1.46 rows=57 width=8) (actual time=0.005..0.008 rows=1 loops=4) 
          Index Cond: (dst = e2.src) 
          Buffers: shared hit=16 
       -> Index Scan using vertex_id_key on vertex v1 (cost=0.42..0.53 rows=1 width=28) (actual time=0.006..0.009 rows=1 loops=4) 
        Index Cond: (id = e1.src) 
        Buffers: shared hit=16 
Planning time: 6.940 ms 
Execution time: 0.714 ms 

А на второй, медленный вызов:

HashAggregate (cost=592.23..592.24 rows=1 width=28) (actual time=89009.873..89009.885 rows=4 loops=1) 
    Group Key: v1.name, v1.id 
    Buffers: shared hit=11644657 read=1240045 
    -> Nested Loop (cost=2.98..592.22 rows=1 width=28) (actual time=9098.961..89009.833 rows=4 loops=1) 
     Buffers: shared hit=11644657 read=1240045 
     -> Nested Loop (cost=2.56..306.89 rows=522 width=32) (actual time=0.424..30066.007 rows=3092522 loops=1) 
       Buffers: shared hit=454795 read=46267 
       -> Nested Loop (cost=2.13..86.31 rows=65 width=36) (actual time=0.306..2120.293 rows=62500 loops=1) 
        Buffers: shared hit=239162 read=12162 
        -> Nested Loop (cost=1.70..51.10 rows=65 width=32) (actual time=0.261..574.490 rows=62500 loops=1) 
          Buffers: shared hit=488 read=562 
actual time=0.205..1.206 rows=25 loops=1)p (cost=1.27..23.95 rows=8 width=36) (--More-- 
           Buffers: shared hit=109 read=17 
           -> Nested Loop (cost=0.85..19.62 rows=8 width=32) (actual time=0.173..0.547 rows=25 loops=1) 
             Buffers: shared hit=12 read=14 
             -> Index Scan using vertex_type_idx on vertex v1 (cost=0.42..8.44 rows=1 width=28) (actual time=0.123..0.153 rows=5 loops=1) 
              Index Cond: (type = 'PLANET'::text) 
              Buffers: shared hit=2 read=4 
             -> Index Scan using edge_src_idx on edge e1 (cost=0.43..10.18 rows=100 width=8) (actual time=0.021..0.039 rows=5 loops=5) 
              Index Cond: (src = v1.id) 
              Buffers: shared hit=10 read=10 
           -> Index Only Scan using vertex_id_key on vertex v2 (cost=0.42..0.53 rows=1 width=4) (actual time=0.009..0.013 rows=1 loops=25) 
             Index Cond: (id = e1.dst) 
             Heap Fetches: 25 
             Buffers: shared hit=97 read=3 
43..2.39 rows=100 width=8) (actual time=0.031..8.504 rows=2500 loops=25)(cost=0.--More-- 
           Index Cond: (src = v2.id) 
           Buffers: shared hit=379 read=545 
        -> Index Only Scan using vertex_id_key on vertex v3 (cost=0.42..0.53 rows=1 width=4) (actual time=0.010..0.013 rows=1 loops=62500) 
          Index Cond: (id = e2.dst) 
          Heap Fetches: 62500 
          Buffers: shared hit=238674 read=11600 
       -> Index Scan using edge_src_idx on edge e3 (cost=0.43..2.39 rows=100 width=8) (actual time=0.013..0.163 rows=49 loops=62500) 
        Index Cond: (src = v3.id) 
        Buffers: shared hit=215633 read=34105 
     -> Index Scan using vertex_id_key on vertex v4 (cost=0.42..0.54 rows=1 width=4) (actual time=0.013..0.013 rows=0 loops=3092522) 
       Index Cond: (id = e3.dst) 
       Filter: ((data @> '{"languages":["spanish"]}'::jsonb) AND (type = 'REGION'::text)) 
       Rows Removed by Filter: 1 
       Buffers: shared hit=11189862 read=1193778 
Planning time: 7.664 ms 
Execution time: 89010.096 ms 
+2

Удалите 'LEFT JOIN'. Они не нужны и могут только путать оптимизатор. –

+2

Внешнее соединение на 'v4' бесполезно, потому что оно эффективно превращается во внутреннее соединение из-за условия' where' –

+1

Как вы справляетесь с ответами ниже, Voluntari? – halfer

ответ

1

[публикуемую в качестве ответа, потому что мне нужно форматирование]

стол край desparately нужен первичный ключ (это означает NOT NULL для {ЦСИ, ДСТ}, который хорошо):

CREATE TABLE IF NOT EXISTS edge 
    (src integer NOT NULL REFERENCES vertex (id) 
    , dst integer NOT NULL REFERENCES vertex (id) 
    , PRIMARY KEY (src,dst) 
    ); 
CREATE UNIQUE INDEX edge_dst_src_idx ON edge (dst, src); 

-- the estimates in the question seem to be off, statistics may be absent. 
VACUUM ANALYZE edge; -- refresh the statistics 
VACUUM ANALYZE vertex; 

И я бы также объединил индексы {type, name} (тип, похоже, имеет очень низкую мощность). Возможно, даже сделать его УНИКАЛЬНЫМ и НЕ НУЛЬНЫМ, но я не знаю ваших данных.

CREATE INDEX vertex_type_name_idx ON vertex (type, name); 
0

Я думаю, что использование подзапроса сделает postgresql неспособным использовать индекс. Поэтому попробуйте выполнить следующий запрос, чтобы проверить улучшение производительности, не используя индекс:

select * from (
SELECT DISTINCT 
    v1.name as name, v1.id as id, v1.type as v1_type 
FROM vertex v1 
    LEFT JOIN edge e1 ON (v1.id = e1.src) 
    LEFT JOIN vertex v2 ON (v2.id = e1.dst) 
    LEFT JOIN edge e2 ON (v2.id = e2.src) 
    LEFT JOIN vertex v3 ON (v3.id = e2.dst) 
    LEFT JOIN edge e3 ON (v3.id = e3.src) 
    LEFT JOIN vertex v4 ON (v4.id = e3.dst) 
WHERE 
    v4.type = 'REGION' AND 
    v4.data @> '{"languages":["spanish"]}'::jsonb 
) t1 
where v1_type = 'PLANET' 
+0

Спасибо за комментарий. Я пробовал подзапрос, и он делает то, что я ожидаю, но, к сожалению, я пытаюсь создать общий построитель запросов. Эти виды конкретных оптимизаций полезны при тестировании, но я начинаю ощущать, что нет общего способа заставить планировщика использовать конкретный индекс перед другим без реорганизации запросов в подзапросы (который побеждает директиву «Generic query builder») , – Voluntari

+0

@Voluntari Я не знаком с postgresql достаточно, но в mysql и oracle можно сказать, что вы не используете индекс. –

+1

@Msfvtp «Я думаю, что использование подзапроса сделает postgresql неспособным использовать индекс« Это будет большой провал любого оптимизатора запросов. Это * конечно * не относится к Oracle, и я сомневаюсь, что это верно для любой РСУБД. –