Query della funzione della finestra lenta con grande tavolo
-
28-09-2020 - |
Domanda
Sto facendo alcuni test delle prestazioni su un nuovo design DB su PostgreSQL 9.4RC1 e sto vedendo alcune query piuttosto lente usando le funzioni della finestra.Ecco la mia configurazione della tabella:
CREATE TABLE player_stat (
player_id VARCHAR(200) NOT NULL,
stat_id BIGINT NOT NULL,
value BIGINT NOT NULL DEFAULT 0,
last_updated TIMESTAMP WITH TIME ZONE NOT NULL,
last_active TIMESTAMP WITH TIME ZONE DEFAULT NULL,
CONSTRAINT player_stat_pk PRIMARY KEY (player_id, stat_id),
CONSTRAINT player_stat_fk1 FOREIGN KEY(stat_id) REFERENCES stat (id)
);
CREATE INDEX player_stat_stat_value_player_desc
ON player_stat (stat_id, value DESC, player_id ASC);
.
Ho inserito 30 milioni di righe in questa tabella divisa tra 3 statistiche:
INSERT INTO player_stat (player_id, stat_id, value, last_updated) SELECT x.id, 1, x.v, now() FROM (SELECT generate_series(1,10000000) as id, trunc(random() * (1900-1200) + 1200) as v) AS x;
INSERT INTO player_stat (player_id, stat_id, value, last_updated) SELECT x.id, 2, x.v, now() FROM (SELECT generate_series(1,10000000) as id, trunc(random() * (1900-1200) + 1200) as v) AS x;
INSERT INTO player_stat (player_id, stat_id, value, last_updated) SELECT x.id, 3, x.v, now() FROM (SELECT generate_series(1,10000000) as id, trunc(random() * (1900-1200) + 1200) as v) AS x;
.
Allora cerco di classificare i giocatori per una determinata stat ( modifica ):
SELECT * FROM
( SELECT player_id
, rank() OVER (ORDER BY value DESC, player_id ASC) as rank
FROM player_stat
WHERE stat_id = 1
) as t
WHERE rank <= 20
ORDER BY rank ASC;
.
Questa query richiede circa 5,5 secondi per tornare.Esecuzione spiegata su di esso restituisce quanto segue:
"Sort (cost=1167612.28..1176082.26 rows=3387993 width=15) (actual time=9726.132..9726.135 rows=20 loops=1)"
" Sort Key: t.rank"
" Sort Method: quicksort Memory: 25kB"
" -> Subquery Scan on t (cost=0.56..684349.57 rows=3387993 width=15) (actual time=0.080..9726.116 rows=20 loops=1)"
" Filter: (t.rank <= 20)"
" Rows Removed by Filter: 9999980"
" -> WindowAgg (cost=0.56..557299.83 rows=10163979 width=15) (actual time=0.077..8351.124 rows=10000000 loops=1)"
" -> Index Only Scan using player_stat_stat_value_player_desc on player_stat (cost=0.56..379430.20 rows=10163979 width=15) (actual time=0.054..2319.007 rows=10000000 loops=1)"
" Index Cond: (stat_id = 1)"
" Heap Fetches: 0"
"Planning time: 0.187 ms"
"Execution time: 9726.172 ms"
.
C'è qualche modo possibile accelerare questo?Il tempo impiegato sembra crescere linearmente con il numero di giocatori nel tavolo.
Soluzione
.C'è un modo in cui posso accelerare questo?
Sì. Non utilizzare una colonna varchar
per un numero integer
. Utilizzare integer
o bigint
se bruciano tanti IDS - molto più piccolo nella tabella e nell'indice e più velocemente da elaborare. Dal momento che sei Classifica 10 milioni di righe Nel tuo test, questo farà una differenza sostanziale.
player_id VARCHAR(200) NOT NULL,
player_id int NOT NULL,
o un uuid
se devi (dubito che):
La tua query classifica 10 milioni di righe. Ci vorrà un po 'di tempo, anche quando leggi dall'indice direttamente e nessun passaggio.
Nota laterale: se si inseriscono innanzitutto le righe Inserire Bulk e aggiungi il vincolo dell'indice e PK (e il vincolo fk) dopo che sarà REINDEX
o VACUUM FULL
.
Assicurarsi che ANALYZE
sia stato eseguito sulla tabella prima del test delle prestazioni, però.
Cosa non hai chiesto
.. ma, uscire con un arto qui, cosa probabilmente cerca.
L'uscita EXPLAIN
rivela che si filtrano le prime 20 righe: (t.rank <= 20)
. La tua query presentata non mostra quella . La query che corrisponde in realtà l'uscita EXPLAIN
sarebbe:
SELECT * FROM (
SELECT player_id
, rank() OVER (ORDER BY value DESC, player_id ASC) AS rank
FROM player_stat
WHERE stat_id = 1
) t
WHERE t.rank <= 20;
.
che può essere migliorato
SELECT row_number() OVER (ORDER BY value DESC, player_id ASC) AS rank
, player_id
FROM player_stat
WHERE stat_id = 1
ORDER BY value DESC, player_id
LIMIT 20;
.
Spiegazione
- .
-
La parte importante per le prestazioni è la clausola
LIMIT
in combinazione conORDER BY
corrispondente all'indice: ora la query legge esattamente 20 righe dall'alto verso l'indice, dove ha dovuto leggere 10000000 nella versione originale. Utilizziamo soloplayer_id
evalue
, quindi possiamo ancora avere una scansione solo in indice. Il resto è arachidi. -
Questo è tutto dovuto al sequenza di eventi in una query
SELECT
: le funzioni della finestra sono applicate PrimaLIMIT
. Solo se l'ordinamento è d'accordo, non dobbiamo considerare il resto delle righe di 10000000 applicabili. -
Possiamo utilizzare
LIMIT 20
perché i primi 20 gradi sono garantiti per durare non più di 20 righe. Il PK su(player_id, stat_id)
garantisce unplayer_id
etagCodetagCodeTagCode unico e poiché è incluso nelstat_id
, ogni rango è assegnato solo una volta - che significa anche che possiamo usare anche ilORDER BY
invece.