2013-05-29 2 views
6

У меня в настоящее время есть запрос postgresql, который медленный из-за инструкции OR. Из-за этого, по-видимому, он не использует индекс. Снова переписать этот запрос.Замедленный оператор OR в postgresql

Запрос:

EXPLAIN ANALYZE SELECT a0_.id AS id0 
FROM advert a0_ 
     INNER JOIN advertcategory a1_ 
       ON a0_.advert_category_id = a1_.id 
WHERE a0_.advert_category_id IN (1136) 
     OR a1_.parent_id IN (1136) 
ORDER BY a0_.created_date DESC 
LIMIT 15; 

                      QUERY PLAN                     
------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
Limit (cost=0.00..27542.49 rows=15 width=12) (actual time=1.658..50.809 rows=15 loops=1) 
    -> Nested Loop (cost=0.00..1691109.07 rows=921 width=12) (actual time=1.657..50.790 rows=15 loops=1) 
     -> Index Scan Backward using advert_created_date_idx on advert a0_ (cost=0.00..670300.17 rows=353804 width=16) (actual time=0.013..16.449 rows=12405 loops=1) 
     -> Index Scan using advertcategory_pkey on advertcategory a1_ (cost=0.00..2.88 rows=1 width=8) (actual time=0.002..0.002 rows=0 loops=12405) 
       Index Cond: (id = a0_.advert_category_id) 
       Filter: ((a0_.advert_category_id = 1136) OR (parent_id = 1136)) 
       Rows Removed by Filter: 1 
Total runtime: 50.860 ms 

Причина медлительности: Filter: ((a0_.advert_category_id = 1136) OR (parent_id = 1136))

Я попытался с помощью INNER JOIN вместо WHERE заявление:

EXPLAIN ANALYZE SELECT a0_.id AS id0 
FROM advert a0_ 
     INNER JOIN advertcategory a1_ 
       ON a0_.advert_category_id = a1_.id 
        AND (a0_.advert_category_id IN (1136) 
         OR a1_.parent_id IN (1136)) 
ORDER BY a0_.created_date DESC 
LIMIT 15; 

                       QUERY PLAN                     
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
Limit (cost=0.00..27542.49 rows=15 width=12) (actual time=4.667..139.955 rows=15 loops=1) 
    -> Nested Loop (cost=0.00..1691109.07 rows=921 width=12) (actual time=4.666..139.932 rows=15 loops=1) 
     -> Index Scan Backward using advert_created_date_idx on advert a0_ (cost=0.00..670300.17 rows=353804 width=16) (actual time=0.019..100.765 rows=12405 loops=1) 
     -> Index Scan using advertcategory_pkey on advertcategory a1_ (cost=0.00..2.88 rows=1 width=8) (actual time=0.002..0.002 rows=0 loops=12405) 
       Index Cond: (id = a0_.advert_category_id) 
       Filter: ((a0_.advert_category_id = 1136) OR (parent_id = 1136)) 
       Rows Removed by Filter: 1 
Total runtime: 140.048 ms 

Запрос ускоряющий при удалении один из критериев OR. Поэтому я сделал СОЮЗ, чтобы увидеть результаты. Это очень быстро! Но я не считаю это решение:

EXPLAIN ANALYZE 
(SELECT a0_.id AS id0 
FROM advert a0_ 
     INNER JOIN advertcategory a1_ 
       ON a0_.advert_category_id = a1_.id 
WHERE a0_.advert_category_id IN (1136) 
ORDER BY a0_.created_date DESC 
LIMIT 15) 
UNION 
(SELECT a0_.id AS id0 
FROM advert a0_ 
     INNER JOIN advertcategory a1_ 
       ON a0_.advert_category_id = a1_.id 
WHERE a1_.parent_id IN (1136) 
ORDER BY a0_.created_date DESC 
LIMIT 15); 

                       QUERY PLAN                      
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
HashAggregate (cost=4125.70..4126.00 rows=30 width=12) (actual time=7.945..7.951 rows=15 loops=1) 
    -> Append (cost=1120.82..4125.63 rows=30 width=12) (actual time=6.811..7.929 rows=15 loops=1) 
     -> Subquery Scan on "*SELECT* 1" (cost=1120.82..1121.01 rows=15 width=12) (actual time=6.810..6.840 rows=15 loops=1) 
       -> Limit (cost=1120.82..1120.86 rows=15 width=12) (actual time=6.809..6.825 rows=15 loops=1) 
        -> Sort (cost=1120.82..1121.56 rows=295 width=12) (actual time=6.807..6.813 rows=15 loops=1) 
          Sort Key: a0_.created_date 
          Sort Method: top-N heapsort Memory: 25kB 
          -> Nested Loop (cost=10.59..1113.59 rows=295 width=12) (actual time=1.151..6.639 rows=220 loops=1) 
           -> Index Only Scan using advertcategory_pkey on advertcategory a1_ (cost=0.00..8.27 rows=1 width=4) (actual time=1.030..1.033 rows=1 loops=1) 
             Index Cond: (id = 1136) 
             Heap Fetches: 1 
           -> Bitmap Heap Scan on advert a0_ (cost=10.59..1102.37 rows=295 width=16) (actual time=0.099..5.287 rows=220 loops=1) 
             Recheck Cond: (advert_category_id = 1136) 
             -> Bitmap Index Scan on idx_54f1f40bd4436821 (cost=0.00..10.51 rows=295 width=0) (actual time=0.073..0.073 rows=220 loops=1) 
              Index Cond: (advert_category_id = 1136) 
     -> Subquery Scan on "*SELECT* 2" (cost=3004.43..3004.62 rows=15 width=12) (actual time=1.072..1.072 rows=0 loops=1) 
       -> Limit (cost=3004.43..3004.47 rows=15 width=12) (actual time=1.071..1.071 rows=0 loops=1) 
        -> Sort (cost=3004.43..3005.99 rows=626 width=12) (actual time=1.069..1.069 rows=0 loops=1) 
          Sort Key: a0_.created_date 
          Sort Method: quicksort Memory: 25kB 
          -> Nested Loop (cost=22.91..2989.07 rows=626 width=12) (actual time=1.056..1.056 rows=0 loops=1) 
           -> Index Scan using idx_d84ab8ea727aca70 on advertcategory a1_ (cost=0.00..8.27 rows=1 width=4) (actual time=1.054..1.054 rows=0 loops=1) 
             Index Cond: (parent_id = 1136) 
           -> Bitmap Heap Scan on advert a0_ (cost=22.91..2972.27 rows=853 width=16) (never executed) 
             Recheck Cond: (advert_category_id = a1_.id) 
             -> Bitmap Index Scan on idx_54f1f40bd4436821 (cost=0.00..22.70 rows=853 width=0) (never executed) 
              Index Cond: (advert_category_id = a1_.id) 
Total runtime: 8.940 ms 
(28 rows) 

Пробовал реверсирования заявление в:

EXPLAIN ANALYZE SELECT a0_.id AS id0 
FROM advert a0_ 
     INNER JOIN advertcategory a1_ 
       ON a0_.advert_category_id = a1_.id 
WHERE 1136 IN (a0_.advert_category_id, a1_.parent_id) 
ORDER BY a0_.created_date DESC 
LIMIT 15; 

                       QUERY PLAN                     
------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
Limit (cost=0.00..27542.49 rows=15 width=12) (actual time=1.848..62.461 rows=15 loops=1) 
    -> Nested Loop (cost=0.00..1691109.07 rows=921 width=12) (actual time=1.847..62.441 rows=15 loops=1) 
     -> Index Scan Backward using advert_created_date_idx on advert a0_ (cost=0.00..670300.17 rows=353804 width=16) (actual time=0.028..27.316 rows=12405 loops=1) 
     -> Index Scan using advertcategory_pkey on advertcategory a1_ (cost=0.00..2.88 rows=1 width=8) (actual time=0.002..0.002 rows=0 loops=12405) 
       Index Cond: (id = a0_.advert_category_id) 
       Filter: ((1136 = a0_.advert_category_id) OR (1136 = parent_id)) 
       Rows Removed by Filter: 1 
Total runtime: 62.506 ms 
(8 rows) 

Пробовал с помощью EXISTS:

EXPLAIN ANALYZE SELECT a0_.id AS id0 
FROM advert a0_ 
     INNER JOIN advertcategory a1_ 
       ON a0_.advert_category_id = a1_.id 
WHERE EXISTS(SELECT test.id 
       FROM advert test 
        INNER JOIN advertcategory test_cat 
          ON test_cat.id = test.advert_category_id 
       WHERE test.id = a0_.id 
        AND (test.advert_category_id IN (1136) 
          OR test_cat.parent_id IN (1136))) 
ORDER BY a0_.created_date DESC 
LIMIT 15; 

                      QUERY PLAN                   
--------------------------------------------------------------------------------------------------------------------------------------------------------------- 
Limit (cost=45538.18..45538.22 rows=15 width=12) (actual time=524.654..524.673 rows=15 loops=1) 
    -> Sort (cost=45538.18..45540.48 rows=921 width=12) (actual time=524.651..524.658 rows=15 loops=1) 
     Sort Key: a0_.created_date 
     Sort Method: top-N heapsort Memory: 25kB 
     -> Hash Join (cost=39803.59..45515.58 rows=921 width=12) (actual time=497.362..524.436 rows=220 loops=1) 
       Hash Cond: (a0_.advert_category_id = a1_.id) 
       -> Nested Loop (cost=39786.88..45486.21 rows=921 width=16) (actual time=496.748..523.501 rows=220 loops=1) 
        -> HashAggregate (cost=39786.88..39796.09 rows=921 width=4) (actual time=496.705..496.872 rows=220 loops=1) 
          -> Hash Join (cost=16.71..39784.58 rows=921 width=4) (actual time=1.210..496.294 rows=220 loops=1) 
           Hash Cond: (test.advert_category_id = test_cat.id) 
           Join Filter: ((test.advert_category_id = 1136) OR (test_cat.parent_id = 1136)) 
           Rows Removed by Join Filter: 353584 
           -> Seq Scan on advert test (cost=0.00..33134.04 rows=353804 width=8) (actual time=0.002..177.953 rows=353804 loops=1) 
           -> Hash (cost=9.65..9.65 rows=565 width=8) (actual time=0.622..0.622 rows=565 loops=1) 
             Buckets: 1024 Batches: 1 Memory Usage: 22kB 
             -> Seq Scan on advertcategory test_cat (cost=0.00..9.65 rows=565 width=8) (actual time=0.005..0.327 rows=565 loops=1) 
        -> Index Scan using advert_pkey on advert a0_ (cost=0.00..6.17 rows=1 width=16) (actual time=0.117..0.118 rows=1 loops=220) 
          Index Cond: (id = test.id) 
       -> Hash (cost=9.65..9.65 rows=565 width=4) (actual time=0.604..0.604 rows=565 loops=1) 
        Buckets: 1024 Batches: 1 Memory Usage: 20kB 
        -> Seq Scan on advertcategory a1_ (cost=0.00..9.65 rows=565 width=4) (actual time=0.010..0.285 rows=565 loops=1) 
Total runtime: 524.797 ms 

Advert стол (усеченную):

353804 rows 
                    Table "public.advert" 
      Column   |    Type    |      Modifiers      | Storage | Stats target | Description 
-----------------------------+--------------------------------+-----------------------------------------------------+----------+--------------+------------- 
id       | integer      | not null default nextval('advert_id_seq'::regclass) | plain |    | 
advert_category_id   | integer      | not null           | plain |    | 
Indexes: 
    "idx_54f1f40bd4436821" btree (advert_category_id) 
    "advert_created_date_idx" btree (created_date) 
Foreign-key constraints: 
    "fk_54f1f40bd4436821" FOREIGN KEY (advert_category_id) REFERENCES advertcategory(id) ON DELETE RESTRICT 
Has OIDs: no 

Вкладка "Категория" ле (урезанный):

565 rows 

          Table "public.advertcategory" 
    Column | Type |       Modifiers       
-----------+---------+------------------------------------------------------------- 
id  | integer | not null default nextval('advertcategory_id_seq'::regclass) 
parent_id | integer | 
active | boolean | not null 
system | boolean | not null 
Indexes: 
    "advertcategory_pkey" PRIMARY KEY, btree (id) 
    "idx_d84ab8ea727aca70" btree (parent_id) 
Foreign-key constraints: 
    "fk_d84ab8ea727aca70" FOREIGN KEY (parent_id) REFERENCES advertcategory(id) ON DELETE RESTRICT 

Коротких конфигурации сервера:

            version              
-------------------------------------------------------------------------------------------------------------- 
PostgreSQL 9.2.4 on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-3), 64-bit 

      name   | current_setting |  source   
----------------------------+--------------------+---------------------- 
shared_buffers    | 1800MB    | configuration file 
work_mem     | 4MB    | configuration file 

Как вы можете видеть, ни один из правильных решений не дают улучшение скорости. Только решение UNION для разделения оператора OR повышает производительность. Но я не могу использовать это, потому что этот запрос используется через мою структуру ORM с большим количеством других параметров фильтра. Плюс, если я могу это сделать, почему оптимизатор этого не делает? Это действительно простая оптимизация.

Любые намеки на это? Решение этой маленькой проблемы получило бы высокую оценку!

+0

Оптимизатор не сделает этого, потому что это очень очень редко, что профсоюз - это оптимизация, и правила, чтобы создать союз, как один, далеки от общего. Думали ли вы, что для каждого из них есть один метод ORM, а затем добавьте метод, чтобы получить лучшую клиентскую сторону 15. Бит непослушный, но с учетом предела остается небольшим, а критерии просты, а не крупными. –

+0

Возможно. Но я не хочу перестраивать свой репозиторий в UNION для каждого крошечного критерия. Поэтому я мог бы назвать это дважды, разделив аргументы. Тхо ... Мне все равно нужно отсортировать приложение прямо? – mauserrifle

+0

Да, когда вам это было нужно. Вы получите два набора записей (или добавьте один к другому, а затем передадите его во внутреннюю сортировку и отрежьте верхнюю часть 15. Непослушный, но выполнимый, учитывая объем данных, которые вы возвращаете. Другие варианты были бы навязчивыми все это на вашем ORM и посмотреть, что он с ним делает, или переделать свою модель в соответствии с такими потребностями, подумал, что это может вызвать проблемы в других областях. –

ответ

3

«Плюс, если я могу сделать это, почему не оптимизатор сделать это?» - Потому что есть всевозможные случаи, когда это необязательно (из-за агрегатов в подзапросах) или интересно (из-за лучшего индекса) для этого.

. Лучший запрос, который вы, возможно, получите, приведен в ответе Гордона, используя union all вместо union, чтобы избежать сортировки (я полагаю, что категория никогда не является ее собственным родителем, исключая любую возможность дублирования).

В противном случае, обратите внимание, что ваш запрос можно переписать следующим образом:

SELECT a0_.id AS id0 
FROM advert a0_ 
     INNER JOIN advertcategory a1_ 
       ON a0_.advert_category_id = a1_.id 
WHERE a1_.id IN (1136) 
     OR a1_.parent_id IN (1136) 
ORDER BY a0_.created_date DESC 
LIMIT 15; 

Другими словами, вы фильтрации на основе критериев из одной таблицы и сортировки/лимит на основе другого. То, как вы его написали, не позволяет вам использовать хороший индекс, потому что планировщик не признает, что критерии фильтра все из одной таблицы, поэтому он будет nestloop над created_date с фильтром, как вы сейчас делаете , Это не плохой план, заметьте ... На самом деле это правильно, если, например, 1136 не является очень избирательным критерием.

Если вы указали, что вторая таблица является интересной, вы можете получить сканирование растровой кучи, когда категория будет достаточно избирательной, если у вас есть индексы на advertcategory (id) (которые у вас уже есть, если они являются первичными) и на advertcategory (parent_id) (которого вы, вероятно, не имеете в данный момент). Не рассчитывайте на него слишком много, - PG не собирает информацию о коррелированных столбцах, насколько мне известно.

Другая возможность может быть поддерживать массив с агрегированными категориями (с использованием триггеров) в объявлении непосредственно, и использовать индекс GIST на нем:

SELECT a0_.id AS id0 
FROM advert a0_ 
WHERE ARRAY[1136, 1137] && a0_.category_ids -- 1136 OR 1137; use <@ for AND 
ORDER BY a0_.created_date DESC 
LIMIT 15; 

Это технически избыточное, но она хорошо работает для оптимизации этот тип запроса (т. е. фильтрует вложенное дерево категорий, которое дает сложные критерии присоединения) ... Когда PG решает использовать его, вы в конечном итоге сортируете подходящие объявления. (В более старых версиях PG селективность & & была произвольной из-за отсутствия статистики, я смутно помню, как читал журнал изменений, в результате чего улучшились 9,1, 9,2 или 9,3 вещи, предположительно, используя код, аналогичный тому, который используется сборщиком статистики контента tsvector для общего массива В любом случае обязательно используйте последнюю версию PG и не забудьте переписать этот запрос с использованием операторов, которые не смогут использовать индекс gin/gist.)

+0

Спасибо, что фильтрация по отношению к PK делает запрос действительно быстрым. В сочетании с другими критериями фильтра запрос намного медленнее (который я не размещал здесь). Я должен посмотреть, что я могу с этим поделать. Вернемся к нему позже. Спасибо, пока! – mauserrifle

+0

Возможно, вам захочется реализовать эти триггеры для поддержки массива category_ids в рекламе напрямую, если у вас есть запросы gory с ORs и AND. :-) –

+0

Это сумасшествие. Он также исправил другой запрос, который я разместил в stackoverflow. Но мое приложение теперь намного медленнее, потому что кажется, что я должен исправить ALOT от Joins и критериев для фильтрации на PK отношения вместо внешнего ключа. Я продолжу исправлять завтра и надеюсь на это. Спасибо за эту информацию! Я буду держать вас в курсе! :)) – mauserrifle

4

Совершенно новый подход. Ваше условие where находится на двух таблицах, но это кажется ненужным.

Первое изменение будет:

where a1_.id = 1136 or a1_.parent_id = 1136 

Я думаю, что структура вы хотите сканирование по таблице категорий, а затем извлекает из таблицы объявления. Чтобы помочь, вы можете создать индекс на advert(advert_category_id, created_date).

У меня возникнет соблазн написать запрос, переместив предложение where в подзапрос. Я не знаю, если это будет эффект производительности:

SELECT a0_.id AS id0 
FROM advert a0_ INNER JOIN 
     (select ac.* 
     from advertcategory ac 
     where ac.id = 1136 or ac.parent_id = 1136 
     ) ac 
     ON a0_.advert_category_id = ac.id 
ORDER BY a0_.created_date DESC 
LIMIT 15; 
+0

Спасибо, это делает запрос действительно быстрым. В сочетании с другими критериями фильтра запрос намного медленнее (который я не размещал здесь). Я должен посмотреть, могу ли я сделать это для других фильтров. – mauserrifle

Смежные вопросы