PostgreSQL сопоставляет интервал между временем начала и окончания с отметкой времени
Я проектирую систему, которая будет хранить записи, содержащие время начала и окончания. Например:
CREATE TABLE test (
id bigserial PRIMARY KEY,
ts_start timestamp NOT NULL,
ts_end timestamp NOT NULL,
foo bar NOT NULL,
...
);
Теперь я хочу выполнить запросы по этому, чтобы найти все строки, которые перекрываются с определенной отметкой времени. Это приведет к выражению where вроде:
WHERE ts_start <= '2006-4-6 12:34:56' AND ts_end > '2006-4-6 12:34:56'
Я проверил это с огромным количеством сгенерированных тестовых данных, и производительность довольно плохая. Я протестировал его с индексом ts_start и другим индексом ts_end, а также с индексом из нескольких столбцов ts_start и ts_end. Последний дал лучший результат, но он все еще далек от оптимального.
Проблема в том, что postgresql не знает, что ts_end гарантированно будет больше ts_start, поэтому он использует план, который способен находить строки, где ts_end меньше, чем ts_start.
Любые предложения, как решить эту проблему?
Изменить: Для людей, имеющих эту проблему тоже, если вы можете подождать немного дольше, тогда PostgreSQL 9.2 имеет идеальное решение: типы диапазонов. 9.2 находится в бета-версии, а финальная версия, скорее всего, будет в конце 2012 года.
3 ответа
Был "временный postgres" (Google google), но я не знаю, поддерживается ли он до сих пор... Я думаю, что обсуждался вопрос о включении этого типа поиска в postgres, но я не помню его окончательное состояние. Тем не мение:
Пример использования box и gist:
CREATE TABLE segments( start INTEGER NOT NULL, stop INTEGER NOT NULL, range_box BOX NOT NULL );
INSERT INTO segments SELECT n,n+1,BOX(POINT(n,-1),POINT(n+1,1)) FROM generate_series( 1, 1000000 ) n;
CREATE INDEX segments_box ON segments USING gist( range_box );
CREATE INDEX segments_start ON segments(start);
CREATE INDEX segments_stop ON segments(stop);
EXPLAIN ANALYZE SELECT * FROM segments WHERE 300000 BETWEEN start AND stop;
Index Scan using segments_start on segments (cost=0.00..12959.24 rows=209597 width=72) (actual time=91.990..91.990 rows=2 loops=1)
Index Cond: (300000 >= start)
Filter: (300000 <= stop)
Total runtime: 92.023 ms
EXPLAIN ANALYZE SELECT * FROM segments WHERE range_box && '(300000,0,300000,0)'::BOX;
Bitmap Heap Scan on segments (cost=283.49..9740.27 rows=5000 width=72) (actual time=0.036..0.037 rows=2 loops=1)
Recheck Cond: (range_box && '(300000,0),(300000,0)'::box)
-> Bitmap Index Scan on segments_box (cost=0.00..282.24 rows=5000 width=0) (actual time=0.032..0.032 rows=2 loops=1)
Index Cond: (range_box && '(300000,0),(300000,0)'::box)
Total runtime: 0.064 ms
Как вы можете видеть, индекс гистограммы здесь невероятно быстр (1500 раз! Lol) (и вы можете использовать множество операторов, таких как перекрытия, ограничения, ограничения и т. Д.).
http://www.postgresql.org/docs/8.2/static/functions-geometry.html
Вы столкнулись с той же проблемой, что и человек, пытающийся проиндексировать отрезки, а затем запросить, находится ли точка в отрезке. Вы просто не можете сделать это, индексируя каждое измерение отдельно, и вам нужно что-то, что индексирует путем построения какой-то структуры BSP.
Я не уверен, есть ли у PG какой-либо встроенный тип данных для поддержки диапазонов дат, но я уверен, что если вы используете PostGIS для представления диапазонов времени в виде точек в двумерном пространстве, а затем сообщаете PG о геоиндексировании, то вы ' Вы получите максимальную производительность из этого запроса.
Может быть, в pg есть встроенный в мою дату эквивалент моего предложения, но, как я уже сказал, я не знаком с ним. Я знаком с возможностями геометрической индексации pg, и думаю, что вам следует серьезно отнестись к этому как к оптимизации.
Вот наивный пример (хотя я уверен, что запрос будет очень быстрым):
- Представлять каждый временной диапазон в виде прямоугольника от начала координат (0,0) до точки (от, до).
- Включите географическую индексацию.
- По заданному периоду времени P вы можете запросить, находится ли он во времени, проверив, находится ли точка (P, P) внутри прямоугольника с помощью функции, подобной ST_Contains. Этот запрос будет O(log(количество диапазонов)).
иллюстрации:
|
|
|
|
to |
(timestamp) |
|
|
|_________________ (from,to)
|__ |
| |(p,p) |
|__|______________|_______________________
from (timestamp)
Проблема в том, что postgresql не знает, что ts_end гарантированно будет больше ts_start, поэтому он использует план, который способен находить строки, где ts_end меньше, чем ts_start.
В подобных ситуациях вам необходимо повторно выразить свой запрос, чтобы сообщить его Postgres.
Это так же, как если бы вы обращались к lft / rgt во вложенном множестве: если у вас есть дерево, проиндексированное с использованием lft / rgt таким образом, что у детей есть parent_lft < lft
а также lft < rgt
а также parent_lft < parent_rgt
, оптимальный запрос будет опираться на parent_lft < lft
а также lft < parent_rgt
(который ищет индекс на lft
для небольшого диапазона), а не parent_lft < lft
а также rgt < parent_rgt
(который ищет индекс на lft
с одной точки и далее).
Вы находитесь в аналогичной ситуации, когда вы добавляете индекс. Если вы не ограничите ни ts_start, ни ts_end, ни оба, вы увидите большой набор строк.
Теперь я хочу выполнить запросы по этому, чтобы найти все строки, которые перекрываются с определенной отметкой времени. Это приведет к выражению where вроде:
WHERE ts_start <= '2006-4-6 12:34:56' AND ts_end > '2006-4-6 12:34:56'
Для этого конкретного запроса вы можете рассмотреть типы геометрии и использовать индекс GIST.
В частности, если вы установите ts_start и ceil ts_end в полночь, вы можете получить целочисленное представление (например, количество дней с начала эпохи). Затем сохраните последний как индексируемый тип и запросите его, используя условие перекрытия.
В качестве примечания, в последние месяцы обсуждалась возможность добавления какого-либо типа сегмента / события метки времени в список pg-хакеров, но я с треском не могу найти соответствующие ссылки через поиск в Google. Так что... упоминая здесь, на случай, если тебе повезет больше, чем мне.