Você obtém uma exceção usando
now()
porque a função não é IMMUTABLE
(obviamente) e, citando o manual
:Vejo duas maneiras de utilizar um índice parcial (muito mais eficiente):
1. Índice parcial com condição usando constante data:
CREATE INDEX queries_recent_idx ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp;
Supondo
created
é definido como timestamp
. Não funcionaria para fornecer um timestamp
constante para um timestamptz
coluna (timestamp with time zone
). A conversão de timestamp
para timestamptz
(ou vice-versa) depende da configuração de fuso horário atual e é não imutável . Use uma constante de tipo de dados correspondente. Entenda os conceitos básicos de carimbos de data/hora com/sem fuso horário:Solte e recrie esse índice em horas com pouco tráfego, talvez com um cron job diariamente ou semanalmente (ou o que for bom o suficiente para você). Criar um índice é bastante rápido, especialmente um índice parcial que é comparativamente pequeno. Esta solução também não precisa adicionar nada à tabela.
Supondo que sem acesso simultâneo para a tabela, a recriação automática do índice pode ser feita com uma função como esta:
CREATE OR REPLACE FUNCTION f_index_recreate()
RETURNS void
LANGUAGE plpgsql 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$;
Ligar:
SELECT f_index_recreate();
now()
(como você tinha) é o equivalente a CURRENT_TIMESTAMP
e retorna timestamptz
. Transmitir para timestamp
com now()::timestamp
ou use LOCALTIMESTAMP
em vez de. db<>fiddle aqui
Antigo sqlfiddle
Se você tiver que lidar com acesso simultâneo para a tabela, use
DROP INDEX CONCURRENTLY
e CREATE INDEX CONCURRENTLY
. Mas você não pode agrupar esses comandos em uma função porque, por documentação
:Portanto, com duas transações separadas :
CREATE INDEX CONCURRENTLY queries_recent_idx2 ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp; -- your new condition
Então:
DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;
Opcionalmente, renomeie para o nome antigo:
ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;
2. Índice parcial com condição na tag "arquivada"
Adicione um
archived
marque na sua mesa:ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;
UPDATE
a coluna em intervalos de sua escolha para "aposentar" linhas mais antigas e criar um índice como:CREATE INDEX some_index_name ON queries_query (user_sid, created)
WHERE NOT archived;
Adicione uma condição de correspondência às suas consultas (mesmo que pareça redundante) para permitir que ela use o índice. Verifique com
EXPLAIN ANALYZE
se o planejador de consultas pega - ele deve ser capaz de usar o índice para consultas em uma data mais recente. Mas não entenderá condições mais complexas que não correspondam exatamente. Você não precisa descartar e recriar o índice, mas o
UPDATE
na mesa pode ser mais caro do que a recriação do índice e a mesa fica um pouco maior. Eu iria com o primeiro opção (recriação do índice). Na verdade, estou usando essa solução em vários bancos de dados. O segundo incorre em atualizações mais caras.
Ambas as soluções mantêm sua utilidade ao longo do tempo, o desempenho se deteriora lentamente à medida que mais linhas desatualizadas são incluídas no índice.