0

У меня есть таблица с именем «meta» с 2,2 миллионами записей. Эта таблица содержит 3 столбца: productId, ключ и значение. Ключ и значение - это два столбца, содержащие мета-описание продукта.Postgres два подзапроса низкой производительности [модель EAV]

Следующий запрос занимает 2,6 секунды и возвращает 676 результатов (PostgreSQL 8.4.13, CentOS 6.4 64-бит). Этот запрос используется для извлечения всех возможных мета-описаний из определенного фильтра (размера), где пользователь уже фильтрует два других фильтра (год и источник).

Я попробовал решение массив из этой темы, но это только хуже: PostgreSQL IN operator with subquery poor performance

Два подзапросов довольно быстро (75 мс и 178ms), но комбинируя их вызывает проблемы с производительностью. Есть ли способ переписать запрос?

Это текущий запрос:

SELECT DISTINCT ON(value) key, value 
FROM "meta" 
WHERE key = 'size' 
    AND "productId" IN (SELECT "productId" 
     FROM "meta" 
     WHERE "value" = 'ibm' 
      AND "key" = 'source') 
    AND "productId" IN (SELECT "productId" 
     FROM "meta" 
     WHERE "value" >= '1920' 
      AND "value" <= '2010' 
      AND "key" = 'year') 
ORDER BY value 

Со следующими EXPLAIN ANALYZE:

Unique (cost=38829.46..38843.19 rows=564 width=15) (actual time=2674.474..2690.856 rows=676 loops=1) 
    -> Sort (cost=38829.46..38836.32 rows=2745 width=15) (actual time=2674.471..2681.333 rows=66939 loops=1) 
     Sort Key: public."meta".value 
     Sort Method: quicksort Memory: 8302kB 
     -> Hash Join (cost=32075.86..38672.69 rows=2745 width=15) (actual time=472.158..2472.002 rows=66939 loops=1) 
       Hash Cond: (public."meta"."originalId" = public."meta"."productId") 
       -> Nested Loop (cost=15079.41..21563.33 rows=13109 width=23) (actual time=113.873..1013.113 rows=104307 loops=1) 
        -> HashAggregate (cost=15079.41..15089.21 rows=980 width=4) (actual time=113.802..163.805 rows=105204 loops=1) 
          -> Bitmap Heap Scan on "meta" (cost=315.39..15051.42 rows=11196 width=4) (actual time=24.540..68.237 rows=105204 loops=1) 
           Recheck Cond: (((key)::text = 'source'::text) AND ((value)::text = 'KADASTER_WOII_RAF_USAAF'::text)) 
           -> Bitmap Index Scan on "productMetadataKeyValueIndex" (cost=0.00..312.60 rows=11196 width=0) (actual time=23.506..23.506 rows=105204 loops=1) 
             Index Cond: (((key)::text = 'source'::text) AND ((value)::text = 'ibm'::text)) 
        -> Index Scan using "idx_productId" on "meta" (cost=0.00..6.59 rows=1 width=19) (actual time=0.006..0.008 rows=1 loops=105204) 
          Index Cond: (public."meta"."productId" = public."meta"."productId") 
          Filter: ((public."meta".key)::text = 'size'::text) 
       -> Hash (cost=16954.58..16954.58 rows=3350 width=4) (actual time=358.214..358.214 rows=184571 loops=1) 
        -> HashAggregate (cost=16921.08..16954.58 rows=3350 width=4) (actual time=258.149..319.154 rows=184571 loops=1) 
          -> Bitmap Heap Scan on "meta" (cost=1172.62..16825.39 rows=38273 width=4) (actual time=86.725..167.110 rows=184571 loops=1) 
           Recheck Cond: (((key)::text = 'year'::text) AND ((value)::text >= '1920'::text) AND ((value)::text <= '2010'::text)) 
           -> Bitmap Index Scan on "productMetadataKeyIndex" (cost=0.00..1163.05 rows=38273 width=0) (actual time=83.992..83.992 rows=184571 loops=1) 
             Index Cond: (((key)::text = 'year'::text) AND ((value)::text >= '1920'::text) AND ((value)::text <= '2010'::text)) 
Total runtime: 2696.276 ms 

Defined индексов:

idx_productId CREATE INDEX "idx_productId" ON "meta" USING btree ("productId")  
productMetaUnique_id CREATE UNIQUE INDEX "productMetaUnique_id" ON "meta" USING btree ("productId", key)  
productMetadataKeyIndex CREATE INDEX "productMetadataKeyIndex" ON "meta" USING btree (key) 
productMetadataKeyValueIndex CREATE INDEX "productMetadataKeyValueIndex" ON "meta" USING btree (key, value) 
+0

Пожалуйста, покажите, какие индексы вы определили. – mvp

+0

Хороший пример проблем, созданных страшной моделью EAV. Вы пытались объединить два подвыбора в один, используя «UNION ALL»? –

+0

@horse Да, я пытался объединить их с UNION, но он создаст инструкцию OR. Результаты должны быть в BOTH-подзапросах, а не только в одном. –

ответ

0

Во-первых, PostgreSQL 8.1.4 античная. Обновите это, потому что каждый выпуск с (8.2, 8.3, 8.4, 9.0, 9.1 и 9.2) видел улучшения в планировщике запросов.

Затем вы можете переписать свой запрос, чтобы использовать объединения и группу, и, возможно, получить лучший план.

select m1."value" 
from meta as m1 
join meta as m2 on m2."productId" = m1."productId" 
       and m2."key" = 'source' 
       and m2."value" = 'ibm' 
join meta as m3 on m3."productId" = m1."productId" 
       and m3."key" = 'year' 
       and m3."value" between 1920 and 2010 
where m1."key" = 'size' 
group by m1."value" 

Последнее, вероятно, может использовать индекс по (key, product_id) и (product_id, key, value) для индекса только сканировать PG 9.2, избегая табличный поиск в целом.

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

Наконец, если вы действительно хотите сохранить материал в мета, это может быть случай, когда он платит, чтобы пойти туда с существуют операторы:

select val 
from unnest(array['Known', 'Sizes', 'Go', 'Here']::text[]) as val 
where exists (
    select 1 
    from meta as m1 
    join meta as m2 on m2."productId" = m1."productId" 
        and m2."key" = 'source' 
        and m2."value" = 'ibm' 
    join meta as m3 on m3."productId" = m1."productId" 
        and m3."key" = 'year' 
        and m3."value" between 1920 and 2010 
    where m1."key" = 'size' 
     and m1."value" = val 
); 

Это позволит сэкономить вам дорого group by, sort и unique операций.

+0

Благодарим вас за ответ и усилие. Я использую PostgreSQL 8.4.13, 8.1.4 был опечаткой. Я попробую первый запрос и обновить этот пост с результатами. Я использую EAV-модель, потому что у многих продуктов нет (много) мета-описаний. Помещение всех описаний в таблицу продуктов напрямую вызовет много пустых столбцов и упростит гибкость в моем определении. –

+0

«Помещение всех описаний в таблицу продуктов напрямую вызовет много пустых столбцов и, на мой взгляд, сделает более гибкой.» - Вы понимаете, что они хранятся во внешнем магазине, когда все равно слишком большие, не так ли? HTTP: // WWW.postgresql.org/docs/9.2/static/storage-toast.html –

+0

Переписанный запрос с соединениями и группой занимает 863 мс для завершения, намного лучше! Im familier with TOAST, мета-стол не использует его в данный момент. –