Migliorare le prestazioni per ordine con colonne da molte tabelle
-
29-09-2020 - |
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);
.
- .
- Ogni tabella ha 1 milione di righe.
- Tutte le righe in entrambe le tabelle sono distinte.
- Se ordino da una sola colonna, il tempo di esecuzione è ~ 30 ms.
- Non c'è campione senza richiesta.
- Un campione può avere molti
s.code
per lo smaes.barcode
.
Come posso migliorare le prestazioni?
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 VIEW
TagCode. 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
.