sql >> Database >  >> RDS >> PostgreSQL

Datetime-beperking toevoegen aan een gedeeltelijke PostgreSQL-index met meerdere kolommen

Je krijgt een uitzondering met now() omdat de functie niet IMMUTABLE . is (uiteraard) en, onder verwijzing naar de handleiding :

Ik zie twee manieren om een ​​(veel efficiëntere) gedeeltelijke index te gebruiken:

1. Gedeeltelijke index met voorwaarde met behulp van constante datum:

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

Aangenomen created is eigenlijk gedefinieerd als timestamp . Het zou niet werken om een ​​timestamp op te geven constante voor een timestamptz kolom (timestamp with time zone ). De cast van timestamp naar timestamptz (of vice versa) hangt af van de huidige tijdzone-instelling en is niet onveranderlijk . Gebruik een constante van overeenkomend gegevenstype. Begrijp de basisprincipes van tijdstempels met / zonder tijdzone:

Verwijderen en opnieuw maken die index op uren met weinig verkeer, misschien met een cronjob op dagelijkse of wekelijkse basis (of wat goed genoeg voor je is). Het maken van een index gaat vrij snel, vooral een gedeeltelijke index die relatief klein is. Deze oplossing hoeft ook niets aan de tabel toe te voegen.

Ervan uitgaande dat geen gelijktijdige toegang naar de tafel, automatische indexrecreatie kan worden gedaan met een functie als deze:

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$;

Bel:

SELECT f_index_recreate();

now() (zoals je had) is het equivalent van CURRENT_TIMESTAMP en retourneert timestamptz . Casten naar timestamp met now()::timestamp of gebruik LOCALTIMESTAMP in plaats daarvan.

db<>fiddle hier
Oude sqlfiddle

Als u te maken heeft met gelijktijdige toegang naar de tabel, gebruik DROP INDEX CONCURRENTLY en CREATE INDEX CONCURRENTLY . Maar u kunt deze opdrachten niet in een functie inpakken omdat, per documentatie :

Dus met twee afzonderlijke transacties :

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

Dan:

DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;

Optioneel, hernoem naar oude naam:

ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;

2. Gedeeltelijke index met voorwaarde op "gearchiveerde" tag

Voeg een archived . toe tag naar je tafel:

ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;

UPDATE de kolom met intervallen van uw keuze om oudere rijen te "intrekken" en een index te maken zoals:

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

Voeg een overeenkomende voorwaarde toe aan uw zoekopdrachten (zelfs als deze overbodig lijkt) zodat deze de index kan gebruiken. Controleer met EXPLAIN ANALYZE of de queryplanner aanslaat - hij zou de index moeten kunnen gebruiken voor query's op een nieuwere datum. Maar complexere omstandigheden die niet exact overeenkomen, begrijpt het niet.

U hoeft de index niet te verwijderen en opnieuw te maken, maar de UPDATE op tafel kan duurder zijn dan indexrecreatie en de tafel wordt iets groter.

Ik zou voor de eerste . gaan optie (index recreatie). In feite gebruik ik deze oplossing in verschillende databases. De tweede kost duurdere updates.

Beide oplossingen behouden hun bruikbaarheid in de loop van de tijd, de prestaties verslechteren langzaam naarmate meer verouderde rijen in de index worden opgenomen.




  1. slaapstand entiteit is niet toegewezen sinds versie 4.3.2

  2. Rangfunctie in MySQL met Order By-clausule

  3. Foutcode:1005. Kan tabel '...' niet maken (fout:150)

  4. Externe sleutel toevoegen aan meerdere kolommen