Послідовне сканування 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
);

ПИТАННЯ

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базу даних і збільшити статистику, зібрану на стовпцях, які використовуються в запиті, але, здається, нічого не допоможе.

Чи міг би хтось поширитись і висвітлити це чи запропонувати щось інше, що я повинен спробувати?


Допоможе мені, якби ви включили запити, які генерували кожен із цих планів виконання.
Майк Шеррілл 'Згадка про котів'

Різниця в 2 порядки в оціночній кількості рядків і фактичній кількості рядків? Чи правильно я це читаю?
Майк Шеррілл 'Згадка про котів'

@Catcall Додали запит (якийсь фундаментальний, щоб можна було розробити те, що відбувається). Коли ви посилаєтесь на передбачувані рядки, це те, що 200, а потім його фактично повертається 153812?
Марк Девідсон

2
Так, 200 проти 150 тис. Здаються дивними на перший погляд. Чи є вагома причина змішати ліву з'єднання з декартовим продуктом ( FROM data, data_area)? На перший погляд, використання DISTINCT ON без пункту ORDER BY здається поганою ідеєю.
Майк Шеррілл 'Відкликання котів'

objas.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 ви використовуєте.


Дякую за вашу пропозицію, вибачте за мою запізнілу відповідь на ваше повідомлення, я працював над іншими проектами. Ваша пропозиція насправді означає, що запит тепер, здається, надійно використовує індекс для всіх запитів, але я все одно не отримую ефективність, яку я би очікувала з цим. Я зробив аналіз на запит, який містить набагато більше даних тлумачення.depesz.com/s/1yu займає приблизно 4 хвилини, при цьому 95% часу витрачається на сканування INDEX.
Марк Девідсон

Забув згадати, що я використовую версію 9.1.4
Марк Девідсон

В основному сканування індексу відбувається досить швидко, проблема полягає в тому, що воно повторюється кілька мільйонів разів. Що ви отримуєте, якщо SET enable_nestloop=offперед запуском запиту?
дезсо

-1

У послідовників у мене була подібна проблема, яка була на кшталт

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

Проблема полягала в тому, що мій bigint_column "між x і y" мав індекс, але в моєму запиті в основному "всі рядки" в цій таблиці, тому він не використовував індекс (оскільки він все одно повинен був сканувати всю таблицю), але робив послідовне сканування seq_scan. Виправленням для мене було створення нового індексу для "мод" частини рівняння, щоб він міг використовувати це для виразу .


downvoters не соромтесь залишати коментарі щодо того, чому :)
rogerdpack
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.