PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Ottimizza l'operazione INSERT / UPDATE / DELETE

Definizione tabella modificata

Se hai davvero bisogno che quelle colonne siano NOT NULL e hai davvero bisogno della stringa 'default' come predefinito per engine_slug , consiglierei di introdurre i valori predefiniti delle colonne:

COLUMN           |          TYPE           |      Modifiers
-----------------+-------------------------+---------------------
 id              | INTEGER                 | NOT NULL DEFAULT ... 
 engine_slug     | CHARACTER VARYING(200)  | NOT NULL DEFAULT 'default'
 content_type_id | INTEGER                 | NOT NULL
 object_id       | text                    | NOT NULL
 object_id_int   | INTEGER                 |
 title           | CHARACTER VARYING(1000) | NOT NULL
 description     | text                    | NOT NULL DEFAULT ''
 content         | text                    | NOT NULL
 url             | CHARACTER VARYING(1000) | NOT NULL DEFAULT ''
 meta_encoded    | text                    | NOT NULL DEFAULT '{}'
 search_tsv      | tsvector                | NOT NULL
 ...

L'istruzione DDL sarebbe:

ALTER TABLE watson_searchentry ALTER COLUMN  engine_slug DEFAULT 'default';

ecc.

Quindi non devi inserire quei valori manualmente ogni volta.

Inoltre:object_id text NOT NULL, object_id_int INTEGER ? È strano. Immagino che tu abbia le tue ragioni...

Andrò con il tuo requisito aggiornato:

Naturalmente, devi aggiungi un UNICO vincolo per far rispettare i tuoi requisiti:

ALTER TABLE watson_searchentry
ADD CONSTRAINT ws_uni UNIQUE (content_type_id, object_id_int)

Verrà utilizzato l'indice di accompagnamento. Con questa query per cominciare.

A proposito, non uso quasi mai varchar(n) a Postgres. Solo text . Ecco un motivo.

Query con CTE di modifica dei dati

Questo potrebbe essere riscritto come una singola query SQL con espressioni di tabelle comuni che modificano i dati, chiamate anche CTE "scrivibili". Richiede Postgres 9.1 o successivo.
Inoltre, questa query elimina solo ciò che deve essere eliminato e aggiorna ciò che può essere aggiornato.

WITH  ctyp AS (
   SELECT id AS content_type_id
   FROM   django_content_type
   WHERE  app_label = 'web'
   AND    model = 'member'
   )
, sel AS (
   SELECT ctyp.content_type_id
         ,m.id       AS object_id_int
         ,m.id::text AS object_id       -- explicit cast!
         ,m.name     AS title
         ,concat_ws(' ', u.email,m.normalized_name,c.name) AS content
         -- other columns have column default now.
   FROM   web_user    u
   JOIN   web_member  m  ON m.user_id = u.id
   JOIN   web_country c  ON c.id = m.country_id
   CROSS  JOIN ctyp
   WHERE  u.is_active
   )
, del AS (     -- only if you want to del all other entries of same type
   DELETE FROM watson_searchentry w
   USING  ctyp
   WHERE  w.content_type_id = ctyp.content_type_id
   AND    NOT EXISTS (
      SELECT 1
      FROM   sel
      WHERE  sel.object_id_int = w.object_id_int
      )
   )
, up AS (      -- update existing rows
   UPDATE watson_searchentry 
   SET    object_id = s.object_id
         ,title     = s.title
         ,content   = s.content
   FROM   sel s
   WHERE  w.content_type_id = s.content_type_id
   AND    w.object_id_int   = s.object_id_int
   )
               -- insert new rows
INSERT  INTO watson_searchentry (
        content_type_id, object_id_int, object_id, title, content)
SELECT  sel.*  -- safe to use, because col list is defined accordingly above
FROM    sel
LEFT    JOIN watson_searchentry w1 USING (content_type_id, object_id_int)
WHERE   w1.content_type_id IS NULL;
  • La sottoquery su django_content_type restituisce sempre un solo valore? Altrimenti, il CROSS JOIN potrebbe causare problemi.

  • Il primo CTE sel raccoglie le righe da inserire. Nota come scelgo nomi di colonna corrispondenti per semplificare le cose.

  • Nel CTE del Evito di eliminare le righe che possono essere aggiornate.

  • Nel CTE up quelle righe vengono invece aggiornate.

  • Di conseguenza, evito di inserire righe che non sono state eliminate prima nel INSERT finale .

Può essere facilmente racchiuso in una funzione SQL o PL/pgSQL per un uso ripetuto.

Non sicuro per un uso simultaneo intenso. Molto meglio della funzione che avevi, ma non ancora affidabile al 100% contro le scritture simultanee. Ma questo non è un problema secondo le tue informazioni aggiornate.

La sostituzione degli UPDATE con DELETE e INSERT può essere o meno molto più costosa. Internamente ogni AGGIORNAMENTO comporta comunque una nuova versione di riga, a causa di MVCC modello .

La velocità prima

Se non ti interessa davvero preservare le vecchie righe, il tuo approccio più semplice potrebbe essere più veloce:elimina tutto e inserisci nuove righe. Inoltre, il wrapping in una funzione plpgsql consente di risparmiare un po' di sovraccarico di pianificazione. Fondamentalmente la tua funzione, con un paio di piccole semplificazioni e osservando le impostazioni predefinite aggiunte sopra:

CREATE OR REPLACE FUNCTION update_member_search_index()
  RETURNS VOID AS
$func$
DECLARE
   _ctype_id int := (
      SELECT id
      FROM   django_content_type
      WHERE  app_label='web'
      AND    model = 'member'
      );  -- you can assign at declaration time. saves another statement
BEGIN
   DELETE FROM watson_searchentry
   WHERE content_type_id = _ctype_id;

   INSERT INTO watson_searchentry
         (content_type_id, object_id, object_id_int, title, content)
   SELECT _ctype_id, m.id, m.id::int,m.name
         ,u.email || ' ' || m.normalized_name || ' ' || c.name
   FROM   web_member  m
   JOIN   web_user    u USING (user_id)
   JOIN   web_country c ON c.id = m.country_id
   WHERE  u.is_active;
END
$func$ LANGUAGE plpgsql;

Mi astengo persino dall'usare concat_ws() :È sicuro contro NULL valori e semplifica il codice, ma un po' più lento della semplice concatenazione.

Inoltre:

Sarebbe più veloce incorporare la logica in questa funzione, se questa è l'unica volta in cui è necessario il trigger. Altrimenti, probabilmente non ne vale la pena.