Ralentir fonction dans la fenêtre de requête avec grande table
-
28-09-2020 - |
Question
Je suis en train de faire quelques tests de performance sur une nouvelle conception bd PostgreSQL 9.4rc1 et je vois une assez lent requêtes à l'aide de fonctions de la fenêtre.Voici ma table de configuration:
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);
J'ai inséré 30 millions de lignes dans cette table réparti entre les 3 stats:
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;
Ensuite, j'essaie de classer les joueurs pour une stat (MODIFIER):
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;
Cette requête prend environ 5,5 secondes pour revenir.L'exécution Expliquer sur il renvoie les éléments suivants:
"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"
Est-il possible que puis-je accélérer les choses?Le temps semble être de plus en plus de façon linéaire avec le nombre de joueurs à la table.
La solution
Est il possible que je peux accélérer les choses?
Oui. Ne pas utiliser de varchar
colonne pour un integer
numéro.Utilisation integer
ou bigint
si vous brûler que de nombreuses Id - beaucoup plus petit dans la table et l'index et rapide à traiter.Puisque vous êtes classement de 10 millions de lignes dans votre test, cela va faire une différence substantielle.
player_id VARCHAR(200) NOT NULL,
player_id int NOT NULL,
Ou un uuid
si vous devez (j'en doute):
Votre requête rangs de 10 millions de lignes.Cela va prendre un certain temps, même lorsque la lecture à partir de l'index directement et pas de tri étape.
Note de côté:Si vous en vrac-insérer des lignes et ajouter des index et PK contrainte (et FK contrainte) après, ça va être beaucoup plus rapide, plus vous obtenez une parfaite index sans ballonnement sans courir REINDEX
ou VACUUM FULL
.Assurez-vous que ANALYZE
a été exécuté sur la table avant de tester les performances, bien que.
Ce que vous ne demandez pas
..mais, aller sur une branche ici, ce sont probablement à la recherche pour.
L' EXPLAIN
sortie révèle que vous filtrez le top 20 lignes: (t.rank <= 20)
. A présenté votre requête n'est pas de montrer que.La requête en réalité correspondant à votre EXPLAIN
la sortie serait:
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;
Ce qui peut être amélioré façon spectaculaire:
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;
Explication
La partie importante pour les performances est l'
LIMIT
la clause en combinaison avecORDER BY
correspondant à l'index:maintenant la requête lit exactement 20 lignes à partir du haut de l'index, où il a dû lire 10000000 dans votre version d'origine.Nous n'utilisons que desplayer_id
etvalue
, de sorte que nous pouvons encore avoir un indice de la seule analyse.Le reste est de l'arachide.C'est tout en raison de la la séquence des événements dans un
SELECT
requête:les fonctions de la fenêtre sont appliquées avantLIMIT
.Seulement si l'ordre de tri est d'accord, nous n'avons pas à examiner le reste de la 10000000 lignes.Nous pouvons utiliser
LIMIT 20
parce que le top 20 des classements de garantie de durée pas plus de 20 lignes.Le PK sur(player_id, stat_id)
garanties uniqueplayer_id
parstat_id
et vu qu'il est inclus dans leORDER BY
, chaque rang n'est attribué qu'une fois - ce qui signifie également que nous pouvons utiliser le légèrement moins cherrow_number()
au lieu de cela.