Последовательное сканирование PostgreSQL вместо индексного сканирования Почему?

12

Привет всем! У меня проблема с запросом к базе данных PostgreSQL, и мне интересно, может ли кто-нибудь помочь. В некоторых случаях мой запрос игнорирует созданный мной индекс, который используется для объединения двух таблиц dataи data_area. Когда это происходит, он использует последовательное сканирование и приводит к гораздо более медленному запросу.

Последовательное сканирование (~ 5 минут)

Unique  (cost=15368261.82..15369053.96 rows=200 width=1942) (actual time=301266.832..301346.936 rows=153812 loops=1)
   CTE data
     ->  Bitmap Heap Scan on data  (cost=6086.77..610089.54 rows=321976 width=297) (actual time=26.286..197.625 rows=335130 loops=1)
           Recheck Cond: (datasetid = 1)
           Filter: ((readingdatetime >= '1920-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2013-03-11 00:00:00'::timestamp without time zone) AND (depth >= 0::double precision) AND (depth <= 99999::double precision))
           ->  Bitmap Index Scan on data_datasetid_index  (cost=0.00..6006.27 rows=324789 width=0) (actual time=25.462..25.462 rows=335130 loops=1)
                 Index Cond: (datasetid = 1)
   ->  Sort  (cost=15368261.82..15368657.89 rows=158427 width=1942) (actual time=301266.829..301287.110 rows=155194 loops=1)
         Sort Key: data.id
         Sort Method: quicksort  Memory: 81999kB
         ->  Hash Left Join  (cost=15174943.29..15354578.91 rows=158427 width=1942) (actual time=300068.588..301052.832 rows=155194 loops=1)
               Hash Cond: (data_area.area_id = area.id)
               ->  Hash Join  (cost=15174792.93..15351854.12 rows=158427 width=684) (actual time=300066.288..300971.644 rows=155194 loops=1)
                     Hash Cond: (data.id = data_area.data_id)
                     ->  CTE Scan on data  (cost=0.00..6439.52 rows=321976 width=676) (actual time=26.290..313.842 rows=335130 loops=1)
                     ->  Hash  (cost=14857017.62..14857017.62 rows=25422025 width=8) (actual time=300028.260..300028.260 rows=26709939 loops=1)
                           Buckets: 4194304  Batches: 1  Memory Usage: 1043357kB
                           ->  Seq Scan on data_area  (cost=0.00..14857017.62 rows=25422025 width=8) (actual time=182921.056..291687.996 rows=26709939 loops=1)
                                 Filter: (area_id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
               ->  Hash  (cost=108.49..108.49 rows=3349 width=1258) (actual time=2.256..2.256 rows=3349 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 584kB
                     ->  Seq Scan on area  (cost=0.00..108.49 rows=3349 width=1258) (actual time=0.007..0.666 rows=3349 loops=1)
 Total runtime: 301493.379 ms

Сканирование индекса (~ 3 секунды) ( на веб-сайте объяснения.depesz.com )

Unique  (cost=17352256.47..17353067.50 rows=200 width=1942) (actual time=3603.303..3681.619 rows=153812 loops=1)
   CTE data
     ->  Bitmap Heap Scan on data  (cost=6284.60..619979.56 rows=332340 width=297) (actual time=26.201..262.314 rows=335130 loops=1)
           Recheck Cond: (datasetid = 1)
           Filter: ((readingdatetime >= '1920-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2013-03-11 00:00:00'::timestamp without time zone) AND (depth >= 0::double precision) AND (depth <= 99999::double precision))
           ->  Bitmap Index Scan on data_datasetid_index  (cost=0.00..6201.51 rows=335354 width=0) (actual time=25.381..25.381 rows=335130 loops=1)
                 Index Cond: (datasetid = 1)
   ->  Sort  (cost=17352256.47..17352661.98 rows=162206 width=1942) (actual time=3603.302..3623.113 rows=155194 loops=1)
         Sort Key: data.id
         Sort Method: quicksort  Memory: 81999kB
         ->  Hash Left Join  (cost=1296.08..17338219.59 rows=162206 width=1942) (actual time=29.980..3375.921 rows=155194 loops=1)
               Hash Cond: (data_area.area_id = area.id)
               ->  Nested Loop  (cost=0.00..17334287.66 rows=162206 width=684) (actual time=26.903..3268.674 rows=155194 loops=1)
                     ->  CTE Scan on data  (cost=0.00..6646.80 rows=332340 width=676) (actual time=26.205..421.858 rows=335130 loops=1)
                     ->  Index Scan using data_area_pkey on data_area  (cost=0.00..52.13 rows=1 width=8) (actual time=0.006..0.008 rows=0 loops=335130)
                           Index Cond: (data_id = data.id)
                           Filter: (area_id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
               ->  Hash  (cost=1254.22..1254.22 rows=3349 width=1258) (actual time=3.057..3.057 rows=3349 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 584kB
                     ->  Index Scan using area_primary_key on area  (cost=0.00..1254.22 rows=3349 width=1258) (actual time=0.012..1.429 rows=3349 loops=1)
 Total runtime: 3706.630 ms

Структура таблицы

Это структура таблицы для data_areaтаблицы. Я могу предоставить другие таблицы, если это будет необходимо.

CREATE TABLE data_area
(
  data_id integer NOT NULL,
  area_id integer NOT NULL,
  CONSTRAINT data_area_pkey PRIMARY KEY (data_id , area_id ),
  CONSTRAINT data_area_area_id_fk FOREIGN KEY (area_id)
      REFERENCES area (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT data_area_data_id_fk FOREIGN KEY (data_id)
      REFERENCES data (id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE
);

QUERY

WITH data AS (
    SELECT * 
    FROM data 
    WHERE 
        datasetid IN (1) 
        AND (readingdatetime BETWEEN '1920-01-01' AND '2013-03-11') 
        AND depth BETWEEN 0 AND 99999
)
SELECT * 
FROM ( 
    SELECT DISTINCT ON (data.id) data.id, * 
    FROM 
        data, 
        data_area 
        LEFT JOIN area ON area_id = area.id 
    WHERE 
        data_id = data.id 
        AND area_id IN (28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11) 
) as s;

Возвращает 153812строки. Сделано, set enable_seqscan= false;чтобы отключить последовательное сканирование и получить индексный результат.

Я попытался сделать для ANALYSEбазы данных и увеличить статистику по столбцам, используемым в запросе, но, похоже, ничего не помогло.

Может ли кто-нибудь распространять и освещать это или предложить что-то еще, что я должен попробовать?

Марк Дэвидсон
источник
Это помогло бы мне, если бы вы включили запросы, которые генерировали каждый из этих планов выполнения.
Майк Шеррилл 'Cat Recall'
Разница в 2 порядка в предполагаемом количестве строк и фактическом количестве строк? Я правильно понял?
Майк Шеррилл 'Cat Recall'
@Catcall Добавили запрос (довольно важно, чтобы иметь возможность выяснить, что происходит). Когда вы ссылаетесь на оценочные строки, это 200, а затем фактически возвращает 153812?
Марк Дэвидсон
2
Да, 200 против 150К кажется странным с первого взгляда. Есть ли веская причина смешивать левое соединение с декартовым произведением ( FROM data, data_area)? На первый взгляд, использование DISTINCT ON без предложения ORDER BY кажется плохой идеей.
Майк Шеррилл 'Cat Recall'
объяснение.depesz.com/s/Uzin может быть информативным.
Крейг Рингер

Ответы:

8

Обратите внимание на эту строку:

->  Index Scan using data_area_pkey on data_area  (cost=0.00..52.13 rows=1 width=8) 
    (actual time=0.006..0.008 rows=0 loops=335130)

Если вы рассчитываете общую стоимость, учитывая циклы, это так 52.3 * 335130 = 17527299. Это больше, чем 14857017,62 для seq_scanальтернативы. Вот почему он не использует индекс.

Таким образом, оптимизатор переоценивает стоимость сканирования индекса. Я предполагаю, что ваши данные отсортированы по индексу (либо из-за кластерного индекса, либо из-за того, как он был загружен), и / или у вас достаточно кэш-памяти и / или хороший быстрый диск. Следовательно, происходит небольшой случайный ввод / вывод.

Вы должны также проверить correlationин pg_stats, который используется оптимизатором для оценки кластеризации при расчете стоимости индекса, и , наконец , попытаться изменить random_page_costи cpu_index_tuple_cost, чтобы соответствовать вашей системе.

Джоп
источник
Если я что-то упустил, я думаю, что @jop имел в виду 52.13, нет 52.3, что привело бы к 17470326.9 (все еще больше, чем seq_scan)
BotNet
2

Ваш CTE фактически не делает ничего, кроме как «передает» на аутсорсинг несколько WHEREусловий, большинство из которых выглядят эквивалентными WHERE TRUE. Поскольку CTE обычно находятся за границей оптимизации (это означает, что она оптимизируется сама по себе), они могут очень помочь с определенными запросами. В этом случае, однако, я ожидал бы прямо противоположного эффекта.

Я постараюсь переписать запрос так, чтобы он был максимально простым:

SELECT d.id, * 
FROM 
    data d 
    JOIN data_area da ON da.data_id = d.id
    LEFT JOIN area a ON da.area_id = a.id 
WHERE 
    d.datasetid IN (1) 
    AND da.area_id IN (28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11) 
    AND (readingdatetime BETWEEN '1920-01-01' AND '2013-03-11') -- this and the next condition don't do anything, I think
    AND depth BETWEEN 0 AND 99999
;

и затем проверьте, используется ли индекс или нет. Все еще очень возможно, что вам не нужны все выходные столбцы (по крайней мере, два столбца таблицы соединений являются лишними).

Пожалуйста, сообщите и сообщите нам, какую версию PostgreSQL вы используете.

Dezso
источник
Спасибо за ваше предложение, мои извинения за задержку с ответом на ваше сообщение, я работаю над другими проектами. Ваше предложение действительно означает, что запрос теперь, похоже, надежно использует индекс для всех запросов, но я все еще не достигаю ожидаемой производительности. Я выполнил анализ запроса, который содержит гораздо больше данных. Пояснение объяснения.depesz.com/s/1yu занимает около 4 минут, 95% времени уходит на сканирование INDEX.
Марк Дэвидсон
Забыл упомянуть, что я использую версию 9.1.4
Марк Дэвидсон
В основном сканирование индекса выполняется довольно быстро, проблема в том, что оно повторяется несколько миллионов раз. Что вы получите, если SET enable_nestloop=offперед выполнением запроса?
Dezso
-1

Для последователей у меня была похожая проблема, которая была похожа

select * from table where bigint_column between x and y and mod(bigint_column, 10000) == z

Проблема заключалась в том, что мой bigint_column «между x и y» имел индекс, но мой запрос был в основном «всеми строками» в этой таблице, поэтому он не использовал индекс [так как в любом случае ему пришлось сканировать всю таблицу], но делал последовательное сканирование seq_scan. Для меня исправлением было создание нового индекса для «модной» части уравнения, чтобы он мог использовать его в выражении .

rogerdpack
источник
downvoters не стесняйтесь оставлять комментарии относительно того, почему :)
rogerdpack