У меня в PostgreSQL 9.0 довольно большая таблица (500K - 1M строк), которая содержит общую информацию о временном интервале, то есть она определяет, когда строка в другой таблице («функция») является допустимой. Определение выглядит так (слегка упрощенно):
CREATE TABLE feature_timeslice
(
timeslice_id int NOT NULL,
feature_id int NOT NULL,
valid_time_begin timestamp NOT NULL,
valid_time_end timestamp,
sequence_number smallint,
-- Some other columns
CONSTRAINT pk_feature_timeslice PRIMARY KEY (timeslice_id)
-- Some other constraints
)
CREATE INDEX ix_feature_timeslice_feature_id
ON feature_timeslice USING btree (feature_id);
Многие другие таблицы для определенных функций затем присоединяются к нему в timeslice_id
:
CREATE TABLE specific_feature_timeslice
(
timeslice_id int NOT NULL,
-- Other columns
CONSTRAINT pk_specific_feature_timeslice PRIMARY KEY (timeslice_id),
CONSTRAINT fk_specific_feature_timeslice_feature_timeslice FOREIGN KEY (timeslice_id) REFERENCES feature_timeslice (timeslice_id)
)
Может быть несколько временных интервалов с перекрывающимися действительными периодами (время начала / окончания), но тот, который имеет самый высокий sequence_number
, имеет приоритет (опять же, небольшое упрощение, но достаточно близкое). Я хотел бы эффективно найти действующую на данный момент строку для каждого feature_id, поэтому у меня определено представление, например:
CREATE VIEW feature_timeslice_id_now
AS
SELECT timeslice_id
FROM
(
SELECT timeslice_id, rank() OVER
(
PARTITION BY feature_id
ORDER BY sequence_number DESC, timeslice_id DESC
)
FROM feature_timeslice
WHERE (current_timestamp AT TIME ZONE 'UTC', '0'::interval) OVERLAPS (valid_time_begin, COALESCE(valid_time_end, 'infinity'::timestamp))
) subq
WHERE subq.rank = 1
Обычно запрашивается так:
SELECT *
FROM specific_feature_timeslice sf
JOIN feature_timeslice_id_now n USING (timeslice_id)
WHERE sf.name = 'SOMETHING'
Это работает, но все еще слишком медленно - занимает 1-2 секунды, хотя может быть возвращено только 1-5 строк, потому что критерий specific_feature_timeslice
обычно сильно его сужает. (Более сложные запросы, объединяющие несколько представлений функций, очень быстро замедляются.) Я не могу понять, как заставить PostgreSQL сделать это более эффективно. План запроса выглядит следующим образом:
Join Filter: ((r.timeslice_id)::integer = (subq.timeslice_id)::integer)
-> Subquery Scan on subq (cost=32034.36..37876.98 rows=835 width=4) (actual time=2086.125..5243.467 rows=250918 loops=1)
Filter: (subq.rank = 1)
-> WindowAgg (cost=32034.36..35790.33 rows=166932 width=10) (actual time=2086.110..4066.351 rows=250918 loops=1)
-> Sort (cost=32034.36..32451.69 rows=166932 width=10) (actual time=2086.065..2654.971 rows=250918 loops=1)
Sort Key: feature_timeslice.feature_id, feature_timeslice.sequence_number, feature_timeslice.timeslice_id
Sort Method: quicksort Memory: 13898kB
-> Seq Scan on feature_timeslice (cost=0.00..17553.93 rows=166932 width=10) (actual time=287.270..1225.595 rows=250918 loops=1)
Filter: overlaps(timezone('UTC'::text, now()), (timezone('UTC'::text, now()) + '00:00:00'::interval), (valid_time_begin)::timestamp without time zone, COALESCE((valid_time_end)::timestamp without time zone, 'infinity'::timestamp without time zone))
-> Materialize (cost=0.00..1093.85 rows=2 width=139) (actual time=0.002..0.007 rows=2 loops=250918)
-> Seq Scan on specific_feature_timeslice sf (cost=0.00..1093.84 rows=2 width=139) (actual time=1.958..7.674 rows=2 loops=1)
Filter: ((name)::text = 'SOMETHING'::text)
Total runtime: 10319.875 ms
В действительности, я хотел бы выполнить этот запрос для любого заданного времени, а не только текущего времени. У меня есть функция, определенная для этого, которая использует время в качестве аргумента, но запрос «сейчас» является наиболее распространенным сценарием, так что даже если бы я мог только ускорить это, это было бы большим улучшением.
== Редактировать ==
ОК, я попытался нормализовать таблицу, как было предложено в обоих ответах, то есть я переместил valid_time_begin и valid_time_end в отдельную таблицу, time_period
. Я также заменил оконную функцию на WHERE NOT EXISTS ([better candidate time slice])
. В процессе я также обновил до PostgreSQL 9.1. При этом некоторые запросы теперь в два раза быстрее. План запроса выглядит так же, как в ответе wildplasser . Это хорошо, но не так хорошо, как я надеялся - для выбора из одной таблицы характеристик все равно требуется больше секунды.
В идеале, я хотел бы воспользоваться преимуществом избирательности условия ГДЕ, как говорит Эрвин Брандштеттер . Если я создаю запрос вручную, время, которое я получаю, составляет 15-30 мс. Теперь это больше похоже на это! Ручной запрос выглядит примерно так:
WITH filtered_feature AS
(
SELECT *
FROM specific_feature_timeslice sf
JOIN feature_timeslice ft USING (timeslice_id)
WHERE sf.name = 'SOMETHING'
)
SELECT *
FROM filtered_feature ff
JOIN
(
SELECT timeslice_id
FROM filtered_feature candidate
JOIN time_period candidate_time ON candidate.valid_time_period_id = candidate_time.id
WHERE ('2011-09-26', '0'::interval) OVERLAPS (candidate_time.valid_time_begin, COALESCE(candidate_time.valid_time_end, 'infinity'::timestamp))
AND NOT EXISTS
(
SELECT *
FROM filtered_feature better
JOIN time_period better_time ON better.valid_time_period_id = better_time.id
WHERE ('2011-09-26', '0'::interval) OVERLAPS (better_time.valid_time_begin, COALESCE(better_time.valid_time_end, 'infinity'::timestamp))
AND better.feature_id = candidate.feature_id AND better.timeslice_id != candidate.timeslice_id
AND better.sequence_number > candidate.sequence_number
)
) AS ft ON ff.timeslice_id = ft.timeslice_id
К сожалению, это слишком большой и сложный для использования в обычных запросах, которые могут объединить многие другие таблицы. Мне нужен какой-то способ инкапсулировать эту логику в функцию (для произвольного времени) или, по крайней мере, в представление (для текущего времени), но я не могу понять, как это сделать, все еще заставляя планировщик запросов сначала выполнить фильтрацию по определенной функции. Если бы я только мог передать набор строк в функцию - но, насколько я знаю, PostgreSQL не позволяет этого. Есть идеи?
== Заключение ==
Я решил использовать наследование PostgreSQL для решения этой проблемы (см. Мой ответ), но я не смог бы придумать эту идею, если бы не ответ Эрвин Брандштеттер , поэтому награда достается ему , wildplasser * Ответ 1045 * также был очень полезен, потому что он позволил мне убрать ненужную оконную функцию, что ускорило ее. Большое спасибо вам обоим!