Добавить ограничение datetime для частичного индекса PostgreSQL с несколькими столбцами

У меня есть таблица PostgreSQL, называемая queries_query , у которой много столбцов.

Два из этих столбцов, created и user_sid , часто используются вместе в SQL-запросах моим приложением, чтобы определить, сколько запросов задал данный пользователь за последние 30 дней. Очень, очень редко, что я запрашиваю эти статистические данные в любое время старше последних 30 дней.

Вот мой вопрос:

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

CREATE INDEX CONCURRENTLY some_index_name ON queries_query (user_sid, created)

Но я хотел бы еще больше ограничить индекс только заботой о тех запросах, в которых создана дата в течение последних 30 дней. Я пробовал сделать следующее:

CREATE INDEX CONCURRENTLY some_index_name ON queries_query (user_sid, created)
WHERE created >= NOW() - '30 days'::INTERVAL`

Но это вызывает исключение, указывающее, что моя функция должна быть неизменной.

Мне бы хотелось, чтобы эта работа работала, чтобы я мог оптимизировать свой индекс и сократить ресурсы, которые Postgres должны выполнять с этими повторяющимися запросами.

5
nl ja de

1 ответы

Вы получаете исключение в своей попытке использовать now() , потому что функция не является IMMUTABLE (очевидно), и я цитирую руководство здесь :

Все функции и операторы, используемые в определении индекса, должны быть «неизменными» ...

Я вижу два способа использования (гораздо более эффективного) частичного индекса здесь:

1. Частичный индекс с условием, использующим постоянный дата:

CREATE INDEX queries_recent_idx ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp;

Assuming created is actually defined as timestamp. It wouldn't work to provide a timestamp constant for a timestamptz column (timestamp with time zone). The cast from timestamp to timestamptz (or vice versa) depends on the current time zone setting and is not immutable. Use a constant of matching data type. Understand the basics of timestamps with/without time zone:

Drop and recreate that index at hours with low traffic, maybe with a cron job on a daily or weekly basis (or whatever is good enough for you). Creating an index is pretty fast, especially a partial index that is comparatively small. This solution also doesn't need to add anything to the table.

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

CREATE OR REPLACE FUNCTION f_index_recreate()
  RETURNS void AS
$func$
BEGIN
   DROP INDEX IF EXISTS queries_recent_idx;
   EXECUTE format('
      CREATE INDEX queries_recent_idx
      ON queries_query (user_sid, created)
      WHERE created > %L::timestamp'
    , LOCALTIMESTAMP - interval '30 days');  -- timestamp constant
--  , now() - interval '30 days');           -- alternative for timestamptz
END
$func$  LANGUAGE plpgsql;

Вызов:

SELECT f_index_recreate();

now() (like you had) is the equivalent of CURRENT_TIMESTAMP and returns timestamptz. Cast to timestamp with now()::timestamp or use LOCALTIMESTAMP instead.

Tested with Postgres 9.2 - 9.4.
SQL Fiddle.


Если вам нужно иметь дело с параллельным доступом , используйте CREATE INDEX CONCURRENTLY . Но вы не можете перенести эту команду в функцию, потому что на документацию :

... в транзакции может быть выполнена регулярная команда CREATE INDEX   block, но CREATE INDEX CONCURRENTLY не может.

Итак, две отдельные транзакции :

CREATE INDEX CONCURRENTLY queries_recent_idx2 ON queries_query (user_sid, created)
WHERE  created > '2013-01-07 00:00'::timestamp;  -- your new condition

Затем:

DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;

При желании переименуйте старое имя:

ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;

2. Частичный индекс с условием на «архивированном» теге

Добавьте тег archived в таблицу:

ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;

UPDATE the column at intervals of your choosing to "retire" older rows and create an index like:

CREATE INDEX some_index_name ON queries_query (user_sid, created)
WHERE NOT archived;

Добавьте условие соответствия к вашим запросам (даже если оно кажется избыточным), чтобы позволить ему использовать индекс. Обратитесь к EXPLAIN ANALYZE , на который заходит планировщик запросов - он должен иметь возможность использовать индекс для запросов в более новую дату. Но он не поймет более сложные условия, не соответствующие точно.

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

Я бы пошел с опцией first (индекс отдыха). Фактически, я использую это решение в нескольких базах данных. Вторая берет на себя более дорогостоящие обновления.

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

7
добавлено
@rdegges: Этого не должно произойти. Подготовленное заявление? Создайте функцию один раз, а затем просто вызовите ее. Возможно, я был недостаточно ясен. Обновил мой ответ и добавил sqlfiddle, демонстрируя, что он работает с 9.2.
добавлено автор Erwin Brandstetter, источник
Спасибо! Я бы хотел использовать эту функцию PostgreSQL, но на самом деле у меня возникают ошибки при запуске, например: pastie.org/6098033 какие-нибудь идеи?
добавлено автор rdegges, источник
Поиграв с этим немного, я также получаю сообщение о том, что подготовленный оператор формата не существует. Я использую 9.2, если это имеет значение.
добавлено автор rdegges, источник
pgsql – PostgreSQL
pgsql – PostgreSQL
2 429 участник(ов)

Чат про PostgreSQL

DBA - русскоговорящее сообщество
DBA - русскоговорящее сообщество
1 345 участник(ов)

Общаемся и обсуждаем темы, посвященные DBA, PostgreSQL, Redis, MongoDB, MySQL, neo4j, riak и т.д. См. также: @devops_ru, @kubernetes_ru, @docker_ru, @nodejs_ru Рекомендуем сразу отключить уведомления, чтобы пребывание здесь было полезным и комфортным.