Ускорение группового запроса по дате на большой таблице в postgres - PullRequest
11 голосов
/ 13 января 2011

У меня есть таблица с примерно 20 миллионами строк.Ради аргументов, скажем, в таблице есть два столбца - идентификатор и отметка времени.Я пытаюсь подсчитать количество предметов в день.Вот что у меня есть на данный момент.

  SELECT DATE(timestamp) AS day, COUNT(*)
    FROM actions
   WHERE DATE(timestamp) >= '20100101'
     AND DATE(timestamp) <  '20110101'
GROUP BY day;

Без каких-либо индексов для запуска на моей машине требуется около 30 секунд.Вот результат анализа объяснения:

 GroupAggregate  (cost=675462.78..676813.42 rows=46532 width=8) (actual time=24467.404..32417.643 rows=346 loops=1)
   ->  Sort  (cost=675462.78..675680.34 rows=87021 width=8) (actual time=24466.730..29071.438 rows=17321121 loops=1)
         Sort Key: (date("timestamp"))
         Sort Method:  external merge  Disk: 372496kB
         ->  Seq Scan on actions  (cost=0.00..667133.11 rows=87021 width=8) (actual time=1.981..12368.186 rows=17321121 loops=1)
               Filter: ((date("timestamp") >= '2010-01-01'::date) AND (date("timestamp") < '2011-01-01'::date))
 Total runtime: 32447.762 ms

Поскольку я вижу последовательное сканирование, я попытался проиндексировать агрегат даты

CREATE INDEX ON actions (DATE(timestamp));

, который снижает скорость примерно на 50%.

 HashAggregate  (cost=796710.64..796716.19 rows=370 width=8) (actual time=17038.503..17038.590 rows=346 loops=1)
   ->  Seq Scan on actions  (cost=0.00..710202.27 rows=17301674 width=8) (actual time=1.745..12080.877 rows=17321121 loops=1)
         Filter: ((date("timestamp") >= '2010-01-01'::date) AND (date("timestamp") < '2011-01-01'::date))
 Total runtime: 17038.663 ms

Я новичок в этом бизнесе по оптимизации запросов и не знаю, что делать дальше.Любые подсказки, как я мог заставить этот запрос работать быстрее?

- edit -

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

Ответы [ 6 ]

6 голосов
/ 14 января 2011

Есть ли способ разбить таблицу на части?

Да:http://www.postgresql.org/docs/current/static/ddl-partitioning.html

Или создать кеш-таблицу со всеми значениями счетчиков?Или какие-то другие варианты?

Создать таблицу «кеша», безусловно, возможно.Но это зависит от того, как часто вам нужен этот результат и насколько точным он должен быть.

CREATE TABLE action_report
AS
SELECT DATE(timestamp) AS day, COUNT(*)
    FROM actions
   WHERE DATE(timestamp) >= '20100101'
     AND DATE(timestamp) <  '20110101'
GROUP BY day;

Тогда SELECT * FROM action_report своевременно даст вам то, что вы хотите.Затем вы запланируете задание cron для регулярного воссоздания этой таблицы.

Этот подход, конечно, не поможет, если временной диапазон изменяется при каждом запросе или этот запрос выполняется только один раз в день.

2 голосов
/ 13 января 2011

Как правило, большинство баз данных будут игнорировать индексы, если ожидаемое количество возвращаемых строк будет большим. Это связано с тем, что для каждого попадания в индекс необходимо будет также найти строку, чтобы быстрее было выполнить полное сканирование таблицы. Это число составляет от 10000 до 100000. Вы можете поэкспериментировать с этим, сузив диапазон дат и посмотрев, где postgres переключается на использование индекса. В этом случае postgres планирует отсканировать 17 701 674 строки, поэтому ваша таблица довольно большая. Если вы сделаете его очень маленьким, а вам все еще кажется, что postgres делает неправильный выбор, попробуйте выполнить анализ таблицы, чтобы postgres получил правильные приближения.

1 голос
/ 13 января 2011

Похоже, что диапазон охватывает практически все доступные данные.

Это может быть проблемой дизайна.Если вы будете выполнять это часто, лучше создать дополнительный столбец timestamp_date, содержащий только дату.Затем создайте индекс для этого столбца и измените запрос соответствующим образом.Столбец должен поддерживаться триггерами вставки + обновления.

SELECT timestamp_date AS day, COUNT(*)
FROM actions
WHERE timestamp_date >= '20100101'
  AND timestamp_date <  '20110101'
GROUP BY day;

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

SELECT DATE(timestamp) AS day, COUNT(*)
FROM actions
WHERE timestamp >= '20100101'
  AND timestamp <  '20110101'
GROUP BY day;
0 голосов
/ 01 марта 2012

Что вы действительно хотите для таких запросов типа DSS - это таблица дат, которая описывает дни.В языке проектирования баз данных это называется измерением даты.Чтобы заполнить такую ​​таблицу, вы можете использовать код, который я разместил в этой статье: http://www.mockbites.com/articles/tech/data_mart_temporal

Затем в каждой строке таблицы действий введите соответствующий ключ date_.

Ваш запрос станет:

SELECT
   d.full_date, COUNT(*)
FROM actions a 
JOIN date_dimension d 
    ON a.date_key = d.date_key
WHERE d.full_date = '2010/01/01'
GROUP BY d.full_date

Если предположить индексы на ключах и full_date, это будет очень быстро, потому что оно работает с клавишами INT4!

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

0 голосов
/ 13 января 2011

Установите work_mem на 2 ГБ и посмотрите, изменит ли это план. Если это не так, возможно, у вас нет выбора.

0 голосов
/ 13 января 2011

Попробуйте запустить explain analyze verbose ..., чтобы увидеть, использует ли агрегат временный файл. Возможно, увеличьте work_mem, чтобы сделать больше в памяти?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...