Domanda

Usando PostgreSQL 8.4 , sto cercando di consultare due tabelle con 1 milione di record utilizzando ordine con colonne indicizzate delle due tabelle, e sto perdendo prestazioni (con 1 colonna richiede 30 mse con due colonne richiede 5 minuti).Ad esempio .:

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

Informazioni sulla tabella:

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. Ogni tabella ha 1 milione di righe.
  2. Tutte le righe in entrambe le tabelle sono distinte.
  3. Se ordino da una sola colonna, il tempo di esecuzione è ~ 30 ms.
  4. Non c'è campione senza richiesta.
  5. Un campione può avere molti s.code per lo smaes.barcode.
  6. Come posso migliorare le prestazioni?

È stato utile?

Soluzione

Problema

Questo è un problema più complesso di quanto sia ovvio su una rapida occhiata. Stai ordinando per due colonne, ciascuna da una tabella diversa, mentre ti unisci su altre due colonne. Ciò rende impossibile per i postGres utilizzare gli indici forniti e deve predefinito per le scansioni sequenziali costose (molto) costose. Ecco un caso correlato su DBA.se:

Indici

Hai bisogno di due indici per le migliori prestazioni, entrambi già presenti:

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

Ma la query semplice che non è possibile utilizzarle, nemmeno in PG 9.4.

QUERY

Postgres 8.4 è piuttosto un ostacolo da prendere. Ma penso di aver trovato un modo con un CTE ricorsivo:

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

testato e funziona per me in PG 9.4. Utilizza gli indici (in parte nelle scansioni solo in indice in PG 9.2 +).

PG 8.4 ha già ricorsivo CTE. Il resto è di base SQL (anche TABLE è disponibile in PG 8.4 (e può essere sostituito con SELECT * FROM). Spero che non ci siano restrizioni per rovinare la parte, non ho più installazione di PG 8.4.

Spiegazione

Le parti commendicate:

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

Sarebbe necessario se potrebbero esserci esempi senza richieste, che escluso in un commento .

La query si affida a questo Comportamento documentato di CTE ricorsive :

.

Suggerimento: l'algoritmo di valutazione della query ricorsiva produce la sua uscita in PARDE PRINCIPATORI ORDINE DI RICERCA.

Bold enfasi miniera. E su Anche questo :

.

funziona perché l'implementazione di PostgreSQL valuta solo quanti Le righe di una query WITH come sono effettivamente recuperate dalla query genitore . L'uso di questo trucco nella produzione non è raccomandato, perché altro I sistemi potrebbero funzionare in modo diverso. Inoltre, di solito non funzionerà se tu Rendi la query esterna ordinare i risultati della query ricorsive o unirsi a loro ad un altro tavolo.

Bold enfasi miniera. Quindi, è solo garantito per funzionare in PostgreSQL e solo se non aggiungi un altro ORDER BY nella query esterna.

Questo è veloce per un (relativamente) piccolo LIMIT nella query esterna. Per il caso a portata di mano, dovrebbe essere molto veloce . La query sarebbe non va bene per un grande LIMIT .

Funzione PL / PGSQL

L'altra opzione a cui posso pensare è una soluzione procedurale con PLPGSQL. Dovrebbe essere ancora un po 'più veloce, specialmente per le chiamate ripetute:

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

Chiamata:

SELECT * FROM f_demo(21); 
.

MATERIALIZED VIEW

Un grande OFFSET ha un effetto simile come un grande LIMIT. Mentre tutte le righe saltate non si aggiungono a I / O, devono ancora essere calcolate per determinare le prime righe dopo l'offset. Se hai bisogno di questo, utilizzare un MATERIALIZED VIEW con una riga aggiunta numero e un indice su quello.

PostGres 9.3+ ha una funzionalità integrata, con il tuo software obsoleto devi lavorare a lavorare a mano. Non è così complicato, però. Fondamentalmente una tavola istantanea che si è popolata da un'istruzione SELECT predefinita o da VIEWTagCode. Fondamentalmente, crea la tabella come:

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);
.

Salva che selezionare come funzione, vista o testo di query normale. Aggiorna (costoso) in una transazione:

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

colonna barcode ridondante

Il MV diventa più costoso se i tavoli sottostanti cambiano molto e hai bisogno di risultati "correnti". C'è un altro A metà strada Opzione economica: Conservare il barcode nella tabella requests ridondante . Potresti includerlo come 2a colonna nel vincolo FK a samples ("Requests_S_fk") e renderlo generatori ON UPDATE CASCADE per evitare dati obsoleti / conflittuali. Quindi è possibile lavorare con un indice su (barcode, code) in requests.

Per le ultime due opzioni è possibile semplificare la collaborazione con indici direttamente applicabili con una clausola WHERE su valori dalla pagina precedente che sostituisce il OFFSET. Considera questa presentazione su "Pagination ha fatto il Postgresql way "on use-the-index-luke.com .

Altri suggerimenti

prova a fare

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
.

o

    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
.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a dba.stackexchange
scroll top