PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Optimiser l'opération INSERT / UPDATE / DELETE

Définition de table modifiée

Si vous avez vraiment besoin que ces colonnes soient NOT NULL et vous avez vraiment besoin de la chaîne 'default' par défaut pour engine_slug , je conseillerais d'introduire les colonnes par défaut :

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'instruction DDL serait :

ALTER TABLE watson_searchentry ALTER COLUMN  engine_slug DEFAULT 'default';

Etc.

Ainsi, vous n'avez pas à insérer ces valeurs manuellement à chaque fois.

Aussi :object_id text NOT NULL, object_id_int INTEGER ? C'est étrange. Je suppose que vous avez vos raisons...

Je vais accepter votre exigence mise à jour :

Bien sûr, vous devez ajouter un UNIQUE contrainte pour faire respecter vos exigences :

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

L'index ci-joint sera utilisé. Par cette requête pour commencer.

BTW, je n'utilise presque jamais varchar(n) dans Postgres. Juste text . Voici une raison.

Requête avec CTE modifiant les données

Cela pourrait être réécrit comme une seule requête SQL avec des expressions de table communes modifiant les données, également appelées CTE "inscriptibles". Nécessite Postgres 9.1 ou version ultérieure.
De plus, cette requête ne supprime que ce qui doit être supprimé et met à jour ce qui peut être mis à jour.

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 sous-requête sur django_content_type renvoie toujours une seule valeur ? Sinon, le CROSS JOIN pourrait causer des problèmes.

  • Le premier CTE sel regroupe les lignes à insérer. Notez comment je sélectionne les noms de colonnes correspondants pour simplifier les choses.

  • Dans le CTE del J'évite de supprimer les lignes qui peuvent être mises à jour.

  • Dans le CTE up ces lignes sont mises à jour à la place.

  • En conséquence, j'évite d'insérer des lignes qui n'ont pas été supprimées auparavant dans le dernier INSERT .

Peut facilement être encapsulé dans une fonction SQL ou PL/pgSQL pour une utilisation répétée.

Non sécurisé pour une utilisation simultanée intensive. Bien mieux que la fonction que vous aviez, mais toujours pas 100% robuste contre les écritures simultanées. Mais ce n'est pas un problème selon vos informations mises à jour.

Remplacer les UPDATE par DELETE et INSERT peut ou non être beaucoup plus cher. En interne, chaque mise à jour entraîne de toute façon une nouvelle version de ligne, en raison du MVCC modèle .

La vitesse d'abord

Si vous ne vous souciez pas vraiment de conserver les anciennes lignes, votre approche la plus simple peut être plus rapide :supprimez tout et insérez de nouvelles lignes. En outre, l'encapsulation dans une fonction plpgsql permet d'économiser un peu de planification. Votre fonction en gros, avec quelques simplifications mineures et en respectant les valeurs par défaut ajoutées ci-dessus :

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;

Je m'abstiens même d'utiliser concat_ws() :Il est sûr contre NULL valeurs et simplifie le code, mais un peu plus lent que la simple concaténation.

Aussi :

Il serait plus rapide d'incorporer la logique dans cette fonction - si c'est la seule fois où le déclencheur est nécessaire. Sinon, ça ne vaut probablement pas la peine.