У меня есть таблица 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)
Таким образом, планы запросов и время выполнения значительно различаются (почти в три раза), но ни один из них не упоминает индекс. Такое поведение совершенно воспроизводимо на моей машине разработчика: я прошел несколько циклов удаления индекса, несколько раз тестировал запрос, заново создавал индекс, снова тестировал запрос несколько раз. Что тут происходит?
HashAggregate
метода используется индекс (и сортировка не требуется), так что вы получите лучшую производительность. Почему индекс не упоминается в плане, я понятия не имею.explain (analyze true, verbose true) ...
:?Ответы:
VACUUM ANALYZE
имеет значение в вашем примере. Плюс, как предоставил @jjanes , дополнительная статистика для функционального индекса. По документации:Однако создание индекса само по себе не заставляет Postgres собирать статистику. Пытаться:
Ничего не возвращает, пока вы не запустите свой первый
ANALYZE
(VACUUM ANALYZE
или пока не включится демон автоочистки).Теперь вы увидите добавленную статистику.
Так как вся таблица должна быть прочитана в любом случае, Postgres будет использовать последовательное сканирование, если не ожидает, что вычисления
myid/100000
будут достаточно дорогими для переключения, а это не так.Ваш единственный другой шанс - это сканирование только по индексу, если индекс намного меньше таблицы - и выполнены предварительные условия для сканирования только по индексу. Подробности в Postgres Wiki и в руководстве .
Пока этот функциональный индекс не используется, побочная выгода от дополнительной статистики является умеренной. Если бы таблица была доступна только для чтения, стоимость была бы низкой, но опять же, мы, вероятно, сразу увидели бы сканирование только по индексу.
Может быть, вы также можете достичь лучших планов запросов, установив более высокий целевой показатель для статистики
mytable.myid
. Это будет только незначительные расходы. Больше:источник
myid/100000 BETWEEN somevalue AND othervalue
условие, так что индекс будет использоваться в плане запроса в любом случае - я только что задал этот вопрос, потому что я не понимал, почему индекс полезен в случае всей таблицы.WHERE myid BETWEEN somevalue*100000 AND othervalue*100000
(рассмотрите эффекты округления в зависимости от ваших типов), и у вас, вероятно, уже есть простой индексmyid
, так что вы можете обойтись без дополнительного специализированного индекса. Может быть более эффективным.Когда вы создаете индекс выражения, он заставляет PostgreSQL собирать статистику по этому выражению. Имея эту статистику под рукой, теперь она имеет точную оценку числа агрегированных строк, которые запрос возвратит, что позволяет сделать лучший выбор плана.
В частности, в этом случае без этой дополнительной статистики считалось, что хеш-таблица будет слишком большой, чтобы поместиться в work_mem, поэтому он не выбрал этот метод.
источник
work_mem
во внимание значение. Если вы подняли его так, чтобы сортировка вписалась в память, все равно использовали бы тот же план. Позвольте мне отметить здесь, что разница во времени (большая часть) происходит от сортировки внешнего диска.