Question

Utiliser PostgreSQL 8.4, j'essaie de consulter deux tables avec 1 million d'enregistrements en utilisant order by avec les colonnes indexées des deux tables, et je perds en performances (avec 1 colonne prend 30 ms et avec deux colonnes prend 5 minutes).Par exemple.:

select r.code, r.test_code, r.sample_code, s.barcode, s.registry_date
from requests r
inner join samples s on (s.code = r.sample_code)
order by s.barcode  asc , r.code asc
limit 21;

Informations sur le tableau :

CREATE TABLE public.samples (
  code BIGINT NOT NULL,
  barcode VARCHAR(40) NOT NULL,
  registry_date TIMESTAMP WITH TIME ZONE NOT NULL,
  CONSTRAINT samples_pkey PRIMARY KEY(code)
);

CREATE INDEX idx_samp_barcode ON public.samples (barcode);
CREATE INDEX idx_samp_barcode_code ON public.samples (barcode, code);
CREATE INDEX idx_samp_barcode_code_desc ON public.samples (barcode DESC, code DESC);
CREATE INDEX idx_test_identifier_desc ON public.samples (barcode DESC);


CREATE TABLE public.requests (
  code BIGINT NOT NULL,
  test_code INTEGER NOT NULL,
  sample_code BIGINT NOT NULL,
  CONSTRAINT requests_pkey PRIMARY KEY(code),
  CONSTRAINT "Requests_S_fk" FOREIGN KEY (sample_code)
    REFERENCES public.samples(code)
    ON DELETE NO ACTION ON UPDATE NO ACTION NOT DEFERRABLE,
  CONSTRAINT "Requests_T_fk" FOREIGN KEY (test_code)
    REFERENCES public.tests(code)
    ON DELETE NO ACTION ON UPDATE NO ACTION NOT DEFERRABLE
);

CREATE INDEX idx_req_test_code ON public.requests (test_code);
CREATE INDEX idx_req_test_code_desc ON public.requests (test_code DESC);
CREATE INDEX request_sample_code_index ON public.requests (sample_code);
CREATE INDEX requests_sample_code_desc_idx ON public.requests (sample_code DESC, code DESC);
CREATE INDEX requests_sample_code_idx ON public.requests (sample_code, code);
  1. Chaque table comporte 1 million de lignes.
  2. Toutes les lignes des deux tableaux sont distinctes.
  3. Si je commande uniquement par l'une ou l'autre colonne, le temps d'exécution est d'environ 30 ms.
  4. Il n'y a pas d'échantillon sans demande.
  5. Un échantillon peut avoir plusieurs s.code pour le petits.barcode.

Comment puis-je améliorer les performances ?

Était-ce utile?

La solution

Problème

Il s’agit d’un problème plus complexe qu’il n’y paraît à première vue.Vous effectuez un tri sur deux colonnes, chacune provenant d'une table différente, tandis que vous effectuez une jointure sur deux autres colonnes.Cela rend impossible pour Postgres d'utiliser les index fournis et il doit utiliser par défaut des analyses séquentielles (très) coûteuses.Voici un cas connexe sur dba.SE :

Index

Vous avez besoin de deux index pour de meilleures performances, tous deux déjà présents :

CREATE INDEX idx_samp_barcode_code ON public.samples (barcode, code);
CREATE INDEX requests_sample_code_idx ON public.requests (sample_code, code);

Mais la requête simple que vous avez ne peut pas les utiliser, pas même à la page 9.4.

Requête

Postgres 8.4 est un véritable obstacle à franchir.Mais je pense avoir trouvé un moyen avec un CTE récursif :

WITH RECURSIVE cte AS (
   ( -- all parentheses are required
   SELECT r.code, r.test_code, r.sample_code, s.barcode, s.registry_date
   FROM  (
      SELECT s.barcode
      FROM   samples s
      -- WHERE  EXISTS (SELECT 1 FROM requests WHERE r.sample_code = s.code)
      ORDER  BY s.barcode
      LIMIT  1  -- get smallest barcode to start with
      ) s0
   JOIN   samples  s USING (barcode)  -- join all samples with same barcode
   JOIN   requests r ON r.sample_code = s.code
   ORDER  BY r.code  -- start with ordered list
   )

   UNION ALL
   (
   SELECT r.code, r.test_code, r.sample_code, s.barcode, s.registry_date
   FROM  (
      SELECT s.barcode
      FROM   cte c
      JOIN   samples s ON s.barcode > c.barcode 
      -- WHERE  EXISTS (SELECT 1 FROM requests WHERE r.sample_code = s.code)
      ORDER  BY s.barcode
      LIMIT  1  -- get next higher barcode
      ) s0
   JOIN   samples  s USING (barcode)  -- join all samples with same barcode
   JOIN   requests r ON r.sample_code = s.code
   ORDER  BY r.code  -- again, ordered list
   )
   )
TABLE cte
LIMIT 21;

Testé et fonctionne pour moi à la page 9.4.Il utilise les index (en partie dans les analyses d'index uniquement à la page 9.2+).

La page 8.4 contient déjà des CTE récursifs.Le reste est du SQL de base (même TABLE est disponible à la page 8.4 (et peut être remplacé par SELECT * FROM).J'espère qu'il n'y a pas de restrictions pour gâcher la fête, je n'ai plus d'installation de la page 8.4.

Explication

Les parties commentées :

-- WHERE  EXISTS (SELECT 1 FROM requests WHERE r.sample_code = s.code)

serait nécessaire s'il pouvait y avoir des échantillons sans aucune demande, ce que vous exclu dans un commentaire.

La requête repose sur ceci comportement documenté des CTE récursifs:

Conseil:L'algorithme d'évaluation récursive des requêtes produit sa sortie dans la largeur d'abord ordre de recherche.

C'est moi qui souligne en gras.Et sur cela aussi:

Cela fonctionne car l'implémentation de PostgreSQL évalue seulement autant de rangées de WITH requête telle qu'elle est réellement récupérée par la requête parent.L'utilisation de cette astuce en production n'est pas recommandée, car d'autres systèmes pourraient fonctionner différemment.D'habitude, ça ne marchera pas si vous faire la requête externe trier les résultats de la requête récursive ou les joindre à une autre table.

C'est moi qui souligne en gras.Donc, son fonctionnement est garanti uniquement dans PostgreSQL et seulement si vous n'en ajoutez pas un autre. ORDER BY dans la requête externe.

C'est rapide pour un (relativement) petit LIMIT dans la requête externe.Pour le cas présent, cela devrait être très vite.La requête serait pas bon pour un grand LIMIT.

Fonction PL/pgSQL

L'autre option à laquelle je peux penser est une solution procédurale avec plpgsql.Cela devrait être encore un peu plus rapide, surtout pour les appels répétés :

CREATE OR REPLACE FUNCTION f_demo(_limit int)
  RETURNS TABLE (code int8, test_code int, sample_code int8
               , barcode varchar, registry_date timestamptz) AS
$func$
DECLARE
   _barcode text;
   _n       int;
   _rest    int := _limit;  -- init with _limit parameter
BEGIN
   FOR _barcode IN
      SELECT DISTINCT s.barcode
      FROM   samples s
      -- WHERE  EXISTS (SELECT 1 FROM requests WHERE r.sample_code = s.code)
      ORDER  BY s.barcode

   LOOP
      RETURN QUERY
      SELECT r.code, r.test_code, r.sample_code, s.barcode, s.registry_date
      FROM   samples  s
      JOIN   requests r ON r.sample_code = s.code
      WHERE  s.barcode = _barcode
      ORDER  BY r.code
      LIMIT  _limit;

      GET DIAGNOSTICS _n = ROW_COUNT;
      _rest := _rest - _n;
      EXIT WHEN _rest < 1;
   END LOOP;
END
$func$ LANGUAGE plpgsql;

Appel:

SELECT * FROM f_demo(21); 

MATERIALIZED VIEW

Un grand OFFSET a un effet similaire à celui d'un grand LIMIT.Bien que toutes les lignes ignorées ne s'ajoutent pas aux E/S, elles doivent quand même être calculées pour déterminer les premières lignes. après le décalage.Si vous en avez besoin, utilisez un MATERIALIZED VIEW avec un numéro de ligne ajouté et un index à ce sujet.

Postgres 9.3+ a une fonctionnalité intégrée, avec votre logiciel obsolète, vous devez tricoter la solution à la main.Ce n’est pourtant pas si compliqué.Fondamentalement, une table d'instantanés remplie à partir d'un SELECT déclaration ou VIEW, comme.En gros, créez le tableau comme :

CREATE TABLE req_sample AS
SELECT row_number() OVER (ORDER BY s.barcode, r.code) AS rn
     , r.code, r.test_code, r.sample_code, s.barcode, s.registry_date
FROM   requests r
JOIN   samples s on (s.code = r.sample_code)
ORDER  by s.barcode, r.code;

ALTER TABLE req_sample ADD CONSTRAINT req_sample_rk PRIMARY KEY (code);
CREATE INDEX foo ON  req_sample (rn);

Enregistrez ce SELECT en tant que fonction, vue ou texte brut de la requête.Actualiser (coûteux) en une seule transaction :

BEGIN;
-- drop PK & index
TRUNCATE req_sample;
INSERT INTO req_sample SELECT ... ;
-- add PK & index
COMMIT;

Redondant barcode colonne

Le MV devient plus cher si les tables sous-jacentes changent beaucoup et que vous avez besoin de résultats « actuels ».Il y en a un de plus option à moitié bon marché : stocker le barcode dans le requests tableau de manière redondante.Vous pouvez l'inclure comme 2ème colonne dans la contrainte FK pour samples ("Requests_S_fk") et fais ça ON UPDATE CASCADE pour éviter les données obsolètes / contradictoires.Ensuite, vous pouvez travailler avec un index sur (barcode, code) dans requests.

Pour les deux dernières options vous pouvez alors simplifier la pagination avec des index directement applicables avec un WHERE clause sur les valeurs de la page précédente remplaçant la OFFSET.Considérez cette présentation sur "Pagination effectuée à la manière de PostgreSQL" sur use-the-index-luke.com.

Autres conseils

essaye de faire

select * from 
(
select
r.code, r.test_code, r.sample_code, s.barcode, s.registry_date,
 from requests r
inner join samples s on (s.code = r.sample_code)
order by s.barcode  asc   limit 21
) as tbl 
 order by tbl.code asc

ou

    select * from 
(
select
r.code, r.test_code, r.sample_code, s.barcode, s.registry_date,
 from requests r
inner join samples s on (s.code = r.sample_code)    
) as tbl 
 order by tbl.barcode  asc , tbl.code asc limit 21
Licencié sous: CC-BY-SA avec attribution
Non affilié à dba.stackexchange
scroll top