Ottieni un'eccezione usando now()
perché la funzione non è IMMUTABLE
(ovviamente) e, citando il manuale
:
Vedo due modi per utilizzare un indice parziale (molto più efficiente):
1. Indice parziale con condizione che utilizza costante data:
CREATE INDEX queries_recent_idx ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp;
Supponendo created
è effettivamente definito come timestamp
. Non funzionerebbe per fornire un timestamp
costante per un timestamptz
colonna (timestamp with time zone
). Il cast di timestamp
a timestamptz
(o viceversa) dipende dall'impostazione del fuso orario corrente e non è immutabile . Utilizzare una costante di tipo di dati corrispondente. Comprendi le basi dei timestamp con/senza fuso orario:
Rilascia e ricrea quell'indice nelle ore con basso traffico, magari con un lavoro cron su base giornaliera o settimanale (o qualunque cosa sia abbastanza buona per te). La creazione di un indice è piuttosto veloce, specialmente un indice parziale che è relativamente piccolo. Anche questa soluzione non ha bisogno di aggiungere nulla alla tabella.
Presupponendo nessun accesso simultaneo alla tabella, la ricreazione automatica dell'indice potrebbe essere eseguita con una funzione come questa:
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$;
Chiama:
SELECT f_index_recreate();
now()
(come te) è l'equivalente di CURRENT_TIMESTAMP
e restituisce timestamptz
. Trasmetti a timestamp
con now()::timestamp
oppure usa LOCALTIMESTAMP
invece.
db<>violino qui
Vecchio sqlfiddle
Se hai a che fare con accesso simultaneo alla tabella, usa DROP INDEX CONCURRENTLY
e CREATE INDEX CONCURRENTLY
. Ma non puoi racchiudere questi comandi in una funzione perché, per documentazione
:
Quindi, con due transazioni separate :
CREATE INDEX CONCURRENTLY queries_recent_idx2 ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp; -- your new condition
Quindi:
DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;
Facoltativamente, rinomina con il vecchio nome:
ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;
2. Indice parziale con condizione su tag "archiviato"
Aggiungi un archived
tagga il tuo tavolo:
ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;
UPDATE
la colonna a intervalli di tua scelta per "ritirare" le righe precedenti e creare un indice come:
CREATE INDEX some_index_name ON queries_query (user_sid, created)
WHERE NOT archived;
Aggiungi una condizione di corrispondenza alle tue query (anche se sembra ridondante) per consentirgli di utilizzare l'indice. Verifica con EXPLAIN ANALYZE
se il pianificatore di query interviene - dovrebbe essere in grado di utilizzare l'indice per le query in una data più recente. Ma non capirà condizioni più complesse che non corrispondono esattamente.
Non è necessario rilasciare e ricreare l'indice, ma l'UPDATE
sul tavolo potrebbe essere più costoso della ricreazione dell'indice e il tavolo diventa leggermente più grande.
Andrei con il primo opzione (ricreazione dell'indice). In effetti, sto usando questa soluzione in diversi database. Il secondo comporta aggiornamenti più costosi.
Entrambe le soluzioni mantengono la loro utilità nel tempo, le prestazioni si deteriorano lentamente man mano che le righe più obsolete vengono incluse nell'indice.