Оптимизировать запрос даты для больших дочерних таблиц: GiST или GIN? - PullRequest
1 голос
/ 20 мая 2010

Задача

72 дочерних таблицы, каждая из которых имеет годовой индекс и индекс станции, определяются следующим образом:

CREATE TABLE climate.measurement_12_013
(
-- Inherited from table climate.measurement_12_013:  id bigint NOT NULL DEFAULT nextval('climate.measurement_id_seq'::regclass),
-- Inherited from table climate.measurement_12_013:  station_id integer NOT NULL,
-- Inherited from table climate.measurement_12_013:  taken date NOT NULL,
-- Inherited from table climate.measurement_12_013:  amount numeric(8,2) NOT NULL,
-- Inherited from table climate.measurement_12_013:  category_id smallint NOT NULL,
-- Inherited from table climate.measurement_12_013:  flag character varying(1) NOT NULL DEFAULT ' '::character varying,
  CONSTRAINT measurement_12_013_category_id_check CHECK (category_id = 7),
  CONSTRAINT measurement_12_013_taken_check CHECK (date_part('month'::text, taken)::integer = 12)
)
INHERITS (climate.measurement)

CREATE INDEX measurement_12_013_s_idx
  ON climate.measurement_12_013
  USING btree
  (station_id);
CREATE INDEX measurement_12_013_y_idx
  ON climate.measurement_12_013
  USING btree
  (date_part('year'::text, taken));

(ограничения внешнего ключа будут добавлены позже.)

Следующий запрос выполняется крайне медленно из-за полного сканирования таблицы:

SELECT
  count(1) AS measurements,
  avg(m.amount) AS amount
FROM
  climate.measurement m
WHERE
  m.station_id IN (
    SELECT
      s.id
    FROM
      climate.station s,
      climate.city c
    WHERE
        /* For one city... */
        c.id = 5182 AND

        /* Where stations are within an elevation range... */
        s.elevation BETWEEN 0 AND 3000 AND

        /* and within a specific radius... */
        6371.009 * SQRT( 
          POW(RADIANS(c.latitude_decimal - s.latitude_decimal), 2) +
            (COS(RADIANS(c.latitude_decimal + s.latitude_decimal) / 2) *
              POW(RADIANS(c.longitude_decimal - s.longitude_decimal), 2))
        ) <= 50
    ) AND

  /* Data before 1900 is shaky; insufficient after 2009. */
  extract( YEAR FROM m.taken ) BETWEEN 1900 AND 2009 AND

  /* Whittled down by category... */
  m.category_id = 1 AND

  /* Between the selected days and years... */
  m.taken BETWEEN
   /* Start date. */
   (extract( YEAR FROM m.taken )||'-01-01')::date AND
    /* End date. Calculated by checking to see if the end date wraps
       into the next year. If it does, then add 1 to the current year.
    */
    (cast(extract( YEAR FROM m.taken ) + greatest( -1 *
      sign(
        (extract( YEAR FROM m.taken )||'-12-31')::date -
        (extract( YEAR FROM m.taken )||'-01-01')::date ), 0
    ) AS text)||'-12-31')::date
GROUP BY
  extract( YEAR FROM m.taken )

Вялость проистекает из этой части запроса:

  m.taken BETWEEN
    /* Start date. */
  (extract( YEAR FROM m.taken )||'-01-01')::date AND
    /* End date. Calculated by checking to see if the end date wraps
      into the next year. If it does, then add 1 to the current year.
    */
    (cast(extract( YEAR FROM m.taken ) + greatest( -1 *
      sign(
        (extract( YEAR FROM m.taken )||'-12-31')::date -
        (extract( YEAR FROM m.taken )||'-01-01')::date ), 0
    ) AS text)||'-12-31')::date

Эта часть запроса соответствует выбору дней. Например, если пользователь хочет просматривать данные в период с 1 июня по 1 июля за все годы, для которых имеются данные, приведенное выше предложение сопоставляется только с этими днями. Если пользователь хочет просмотреть данные в период с 22 декабря по 22 марта, опять же для всех лет, для которых имеются данные, вышеприведенное предложение рассчитывает, что 22 марта наступит в следующем году 22 декабря, и, соответственно, соответствует дате:

image

В настоящее время даты установлены с 1 января по 31 декабря, но будут параметризованы, как показано выше.

HashAggregate от плана показывает стоимость 10006220141.11, что, я подозреваю, на астрономически огромной стороне.

В таблице измерений выполняется полное сканирование таблицы (в которой нет ни данных, ни индексов). Таблица объединяет 273 миллиона строк из своих дочерних таблиц.

Вопрос

Как правильно индексировать даты, чтобы избежать полного сканирования таблицы?

Опции, которые я рассмотрел:

  • GIN
  • GiST
  • Перепишите предложение WHERE
  • Разделение столбцов year_taken, month_taken и day_taken в таблицах

Что ты думаешь?

Спасибо!

Ответы [ 3 ]

2 голосов
/ 20 мая 2010

Ваша проблема в том, что у вас есть предложение where в зависимости от расчета даты. База данных не может использовать индекс, если ей нужно извлечь каждую строку и выполнить для нее расчет, прежде чем узнать, совпадет ли дата.

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

1 голос
/ 20 мая 2010

Попробуйте что-то вроде этого:

create temporary table test (d date);

insert into test select '1970-01-01'::date+generate_series(1,50*365);

analyze test

create function month_day(d date) returns int as $$
  select extract(month from $1)::int*100+extract(day from $1)::int $$
language sql immutable strict;

create index test_d_month_day_idx on test (month_day(d));

explain analyze select * from test
  where month_day(d)>=month_day('2000-04-01')
  and month_day(d)<=month_day('2000-04-05');
0 голосов
/ 21 мая 2010

Я думаю, что для эффективной работы с этими разделами я бы хотел, чтобы ваше приложение было намного умнее в отношении диапазонов дат. Пусть он сгенерирует фактический список дат для проверки на раздел, а затем сгенерирует один запрос с UNION между разделами. Похоже, ваш набор данных довольно статичен, поэтому CLUSTER в индексе даты также может значительно повысить производительность.

...