Индекс, который не используется, но влияет на запрос

8

У меня есть таблица PostgreSQL 9.3 с некоторыми числами и некоторыми дополнительными данными:

CREATE TABLE mytable (
    myid BIGINT,
    somedata BYTEA
)

Эта таблица в настоящее время имеет около 10 миллионов записей и занимает 1 ГБ дискового пространства. myidне являются последовательными.

Я хочу вычислить, сколько строк в каждом блоке из 100000 последовательных чисел:

SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;

Это возвращает около 3500 строк.

Я заметил, что наличие определенного индекса значительно ускоряет этот запрос, хотя план запроса вообще не упоминает об этом. План запроса без индекса:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=1636639.92..1709958.65 rows=496942 width=8) (actual time=6783.763..8888.841 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Sort  (cost=1636639.92..1659008.91 rows=8947594 width=8) (actual time=6783.752..8005.831 rows=8947557 loops=1)
         Output: ((myid / 100000))
         Sort Key: ((mytable.myid / 100000))
         Sort Method: external merge  Disk: 157440kB
         ->  Seq Scan on public.mytable  (cost=0.00..236506.92 rows=8947594 width=8) (actual time=0.020..1674.838 rows=8947557 loops=1)
               Output: (myid / 100000)
 Total runtime: 8914.780 ms
(9 rows)

Индекс:

db=> CREATE INDEX myindex ON mytable ((myid/100000));
db=> VACUUM ANALYZE;

Новый план запроса:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=281242.99..281285.97 rows=3439 width=8) (actual time=3190.189..3190.800 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Seq Scan on public.mytable  (cost=0.00..236505.56 rows=8947485 width=8) (actual time=0.026..1659.571 rows=8947557 loops=1)
         Output: (myid / 100000)
 Total runtime: 3190.975 ms
(5 rows)

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

liori
источник
Я не эксперт по анализу планов запросов Postgres, но я думаю, что для HashAggregateметода используется индекс (и сортировка не требуется), так что вы получите лучшую производительность. Почему индекс не упоминается в плане, я понятия не имею.
ypercubeᵀᴹ
Изменится ли выходной план, если вы включите подробный режим, используя explain (analyze true, verbose true) ...:?
a_horse_with_no_name
Было бы здорово, если бы вы могли свести это в отдельный тестовый набор. Это кажется странным.
Крейг Рингер,
@a_horse_with_no_name: Да, он меняется - я заменил планы запросов подробными в вопросе. Но этот план запроса все еще не упоминает индекс вообще.
Лиори
Если в столбце id с индексом имеется больше доступных статистических данных (особенно значений кардинальности и, возможно, значений min / max), чем без него, это может изменить группу оптимизатора при выборе метода, даже если он вообще не будет использовать индекс , (Я вообще не знаю оптимизатора и статистики postgres, так что понятия не имею, так ли это могло быть или нет.)
Мат

Ответы:

3

VACUUM ANALYZEимеет значение в вашем примере. Плюс, как предоставил @jjanes , дополнительная статистика для функционального индекса. По документации:

pg_statisticтакже хранит статистические данные о значениях выражений индекса. Они описываются так, как если бы они были фактическими столбцами данных; в частности, starelidссылки на указатель. Однако для обычного столбца индекса без выражения запись не создается, поскольку она будет избыточной по сравнению с записью для базового столбца таблицы.

Однако создание индекса само по себе не заставляет Postgres собирать статистику. Пытаться:

CREATE INDEX myindex ON mytable ((myid/100000));
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Ничего не возвращает, пока вы не запустите свой первый ANALYZE( VACUUM ANALYZEили пока не включится демон автоочистки).

ANALYZE mytable;
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Теперь вы увидите добавленную статистику.

Так как вся таблица должна быть прочитана в любом случае, Postgres будет использовать последовательное сканирование, если не ожидает, что вычисления myid/100000будут достаточно дорогими для переключения, а это не так.

Ваш единственный другой шанс - это сканирование только по индексу, если индекс намного меньше таблицы - и выполнены предварительные условия для сканирования только по индексу. Подробности в Postgres Wiki и в руководстве .

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

Может быть, вы также можете достичь лучших планов запросов, установив более высокий целевой показатель для статистики mytable.myid. Это будет только незначительные расходы. Больше:

Эрвин Брандштеттер
источник
Спасибо за это объяснение, это очень полезно для понимания проблемы. В моем случае мне, скорее всего, понадобится дополнительное myid/100000 BETWEEN somevalue AND othervalueусловие, так что индекс будет использоваться в плане запроса в любом случае - я только что задал этот вопрос, потому что я не понимал, почему индекс полезен в случае всей таблицы.
Лиори
@liori: вы можете покрыть это с помощью WHERE myid BETWEEN somevalue*100000 AND othervalue*100000(рассмотрите эффекты округления в зависимости от ваших типов), и у вас, вероятно, уже есть простой индекс myid, так что вы можете обойтись без дополнительного специализированного индекса. Может быть более эффективным.
Эрвин Брандштеттер
6

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

В частности, в этом случае без этой дополнительной статистики считалось, что хеш-таблица будет слишком большой, чтобы поместиться в work_mem, поэтому он не выбрал этот метод.

jjanes
источник
Я думаю, что планировщик не принимает work_memво внимание значение. Если вы подняли его так, чтобы сортировка вписалась в память, все равно использовали бы тот же план. Позвольте мне отметить здесь, что разница во времени (большая часть) происходит от сортировки внешнего диска.
Дезсо
1
@dezso Что если вы экспериментально удвоите или утроите значение work_mem, необходимое для размещения сортировки в памяти? У сортировки и хеширования разные оценки, и сами оценки не очень точны. Кроме того, какую минорную версию 9.3 вы используете?
Джанес