PostgreSQLquery скорость является переменной
контекст
У меня есть таблица, в которой хранятся данные сетевого потока (все пакеты перехватываются маршрутизатором). Эта таблица в настоящий момент содержит около 5,9 миллионов строк.
проблема
Я пытаюсь простой запрос, чтобы посчитать количество пакетов, полученных за день, что не должно занять много времени.
При первом запуске запрос занимает 88 секунд, затем после второго запуска 33 секунды, а затем 5 секунд для всех последующих запусков.
Основная проблема не в скорости запроса, а в том, что после выполнения одного и того же запроса в 3 раза скорость увеличивается почти в 20 раз.
Я понимаю концепцию кеша запросов, однако производительность исходного прогона запросов для меня не имеет смысла.
тесты
Столбец, который я использую для соединения (datetime), имеет тип timestamptz
и индексируется:
CREATE INDEX date ON netflows USING btree (datetime);
Глядя на EXPLAIN
заявления. Разница в исполнении заключается в Nested Loop
,
я уже VACUUM ANALYZE
таблица, с точно такими же результатами.
Текущая среда
- Linux Ubuntu 12.04 VM работает на VMware ESX 4.1
- PostgreSQL 9.1
- ВМ имеет 2 ГБ оперативной памяти, 2 ядра.
- сервер базы данных полностью посвящен этому и больше ничего не делает
- вставляет в таблицу каждую минуту (100 строк в минуту)
- очень низкая активность диска, оперативной памяти или процессора
запрос
with date_list as (
select
series as start_date,
series + '23:59:59' as end_date
from
generate_series(
(select min(datetime) from netflows)::date,
(select max(datetime) from netflows)::date,
'1 day') as series
)
select
start_date,
end_date,
count(*)
from
netflows
inner join date_list on (datetime between start_date and end_date)
group by
start_date,
end_date;
Объяснение первого запуска (88 секунд)
Sort (cost=27007355.59..27007356.09 rows=200 width=8) (actual time=89647.054..89647.055 rows=18 loops=1)
Sort Key: date_list.start_date
Sort Method: quicksort Memory: 25kB
CTE date_list
-> Function Scan on generate_series series (cost=0.13..12.63 rows=1000 width=8) (actual time=92.567..92.667 rows=19 loops=1)
InitPlan 2 (returns $1)
-> Result (cost=0.05..0.06 rows=1 width=0) (actual time=71.270..71.270 rows=1 loops=1)
InitPlan 1 (returns $0)
-> Limit (cost=0.00..0.05 rows=1 width=8) (actual time=71.259..71.261 rows=1 loops=1)
-> Index Scan using date on netflows (cost=0.00..303662.15 rows=5945591 width=8) (actual time=71.252..71.252 rows=1 loops=1)
Index Cond: (datetime IS NOT NULL)
InitPlan 4 (returns $3)
-> Result (cost=0.05..0.06 rows=1 width=0) (actual time=11.786..11.787 rows=1 loops=1)
InitPlan 3 (returns $2)
-> Limit (cost=0.00..0.05 rows=1 width=8) (actual time=11.778..11.779 rows=1 loops=1)
-> Index Scan Backward using date on netflows (cost=0.00..303662.15 rows=5945591 width=8) (actual time=11.776..11.776 rows=1 loops=1)
Index Cond: (datetime IS NOT NULL)
-> HashAggregate (cost=27007333.31..27007335.31 rows=200 width=8) (actual time=89639.167..89639.179 rows=18 loops=1)
-> Nested Loop (cost=0.00..23704227.20 rows=660621222 width=8) (actual time=92.667..88059.576 rows=5945457 loops=1)
-> CTE Scan on date_list (cost=0.00..20.00 rows=1000 width=16) (actual time=92.578..92.785 rows=19 loops=1)
-> Index Scan using date on netflows (cost=0.00..13794.89 rows=660621 width=8) (actual time=2.438..4571.884 rows=312919 loops=19)
Index Cond: ((datetime >= date_list.start_date) AND (datetime <= date_list.end_date))
Total runtime: 89668.047 ms
ОБЪЯСНЕНИЕ третьего запуска (5 секунд)
Sort (cost=27011357.45..27011357.95 rows=200 width=8) (actual time=5645.031..5645.032 rows=18 loops=1)
Sort Key: date_list.start_date
Sort Method: quicksort Memory: 25kB
CTE date_list
-> Function Scan on generate_series series (cost=0.13..12.63 rows=1000 width=8) (actual time=0.108..0.204 rows=19 loops=1)
InitPlan 2 (returns $1)
-> Result (cost=0.05..0.06 rows=1 width=0) (actual time=0.050..0.050 rows=1 loops=1)
InitPlan 1 (returns $0)
-> Limit (cost=0.00..0.05 rows=1 width=8) (actual time=0.046..0.046 rows=1 loops=1)
-> Index Scan using date on netflows (cost=0.00..303705.14 rows=5946469 width=8) (actual time=0.046..0.046 rows=1 loops=1)
Index Cond: (datetime IS NOT NULL)
InitPlan 4 (returns $3)
-> Result (cost=0.05..0.06 rows=1 width=0) (actual time=0.026..0.026 rows=1 loops=1)
InitPlan 3 (returns $2)
-> Limit (cost=0.00..0.05 rows=1 width=8) (actual time=0.026..0.026 rows=1 loops=1)
-> Index Scan Backward using date on netflows (cost=0.00..303705.14 rows=5946469 width=8) (actual time=0.026..0.026 rows=1 loops=1)
Index Cond: (datetime IS NOT NULL)
-> HashAggregate (cost=27011335.17..27011337.17 rows=200 width=8) (actual time=5645.005..5645.009 rows=18 loops=1)
-> Nested Loop (cost=0.00..23707741.28 rows=660718778 width=8) (actual time=0.134..4176.406 rows=5946329 loops=1)
-> CTE Scan on date_list (cost=0.00..20.00 rows=1000 width=16) (actual time=0.110..0.343 rows=19 loops=1)
-> Index Scan using date on netflows (cost=0.00..13796.94 rows=660719 width=8) (actual time=0.026..164.117 rows=312965 loops=19)
Index Cond: ((datetime >= date_list.start_date) AND (datetime <= date_list.end_date))
Total runtime: 5645.189 ms
2 ответа
Если вы делаете INNER JOIN
Я не думаю, что вам нужен CTE вообще. Вы можете определить
select
datetime::date,
count(*)
from netflows
group by datetime::date /* or GROUP BY 1 as Postgres extension */
Я не понимаю, почему вам нужна таблица дат, если вы не хотите LEFT JOIN
чтобы получить нули, где это уместно. Это будет означать один проход через данные.
Кстати, я не рекомендую вам использовать ключевые слова, такие как дата и дата / время, для сущностей и столбцов; даже когда это законно, оно того не стоит.
WITH date_list as (
SELECT t AS start_date
,(t + interval '1d') AS end_date
FROM (
SELECT generate_series((min(datetime))::date
,(max(datetime))::date
,'1d') AS t
FROM netflows
) x
)
SELECT d.start_date
,count(*) AS ct
FROM date_list d
LEFT JOIN netflows n ON n.datetime >= d.start_date
AND n.datetime < d.end_date
GROUP BY d.start_date;
И используйте правильное имя для вашего индекса (уже намекнул @Andrew):
CREATE INDEX netflows_date_idx ON netflows (datetime);
Основные моменты
Предполагая, что вы хотите ряд для каждого дня календаря, как @Andrew уже упоминал в своем ответе, я заменил
JOIN
сLEFT JOIN
,Гораздо эффективнее захватить
min()
а такжеmax()
из сетевых потоков в одном запросе.Упрощенный тип литья.
Исправлены диапазоны дат. Ваш код не будет работать для отметок времени, таких как
'2012-12-06 23:59:59.123'
,
Протестировал это на большом столе, и производительность была хорошей.
Что касается вашего первоначального вопроса: несомненно, кеширующие эффекты, которые следует ожидать - особенно с ограниченным объемом оперативной памяти.