J'ai une table avec 165 m records comme ceci:
Performance
id integer
installs integer
hour timestamp without time zone
J'ai aussi un index sur l'heure:
CREATE INDEX hour_idx
ON performance
USING btree
(hour DESC NULLS LAST);
Cependant, sélectionnez Top 10 des enregistrements commandés par heure prend 6 minutes!
EXPLAIN ANALYZE select hour from performance order by hour desc limit 10
Retour
Limit (cost=7952135.23..7952135.25 rows=10 width=8) (actual time=376313.958..376313.964 rows=10 loops=1)
-> Sort (cost=7952135.23..8368461.00 rows=166530310 width=8) (actual time=376313.957..376313.960 rows=10 loops=1)
Sort Key: hour
Sort Method: top-N heapsort Memory: 25kB
-> Seq Scan on performance (cost=0.00..4353475.10 rows=166530310 width=8) (actual time=0.006..327149.828 rows=192330557 loops=1)
Planning time: 0.070 ms
Execution time: 376330.573 ms
Pourquoi cela prend-t-il autant de temps? S'il y a un index à la date du champ de la date - ne devrait-il pas être très rapide de récupérer des données?
Dans votre exemple de code ci-dessus, l'index est explicitement créé comme NULLS LAST
et la requête exécute implicitement NULLS FIRST
(qui est la valeur par défaut pour ORDER BY .. DESC
) Donc, PostgreSQL aurait besoin de résoudre les données s'il utilisait l'index. En conséquence, l'index rendrait la requête plusieurs fois plus lent que même la table (déjà lente).
rds-9.6.5 root@db1=> create table performance (id integer, installs integer, hour timestamp without time zone);
CREATE TABLE
Time: 28.100 ms
rds-9.6.5 root@db1=> with generator as (select generate_series(1,166530) i)
[more] - > insert into performance (
[more] ( > select
[more] ( > i id,
[more] ( > (random()*1000)::integer installs,
[more] ( > (now() - make_interval(secs => i))::timestamp installs
[more] ( > from generator
[more] ( > );
INSERT 0 166530
Time: 244.872 ms
rds-9.6.5 root@db1=> create index hour_idx
[more] - > on performance
[more] - > using btree
[more] - > (hour desc nulls last);
CREATE INDEX
Time: 67.089 ms
rds-9.6.5 root@db1=> vacuum analyze performance;
VACUUM
Time: 43.552 ms
Nous pouvons ajouter une clause WHERE
sur la colonne Hour de sorte que l'utilisation de l'index devienne une bonne idée - mais remarquez comment nous toujours besoin de résoudre les données de l'index.
rds-9.6.5 root@db1=> explain select hour from performance where hour>now() order by hour desc limit 10;
QUERY PLAN
---------------------------------------------------------------------------------------------
Limit (cost=4.45..4.46 rows=1 width=8)
-> Sort (cost=4.45..4.46 rows=1 width=8)
Sort Key: hour DESC
-> Index Only Scan using hour_idx on performance (cost=0.42..4.44 rows=1 width=8)
Index Cond: (hour > now())
(5 rows)
Time: 0.789 ms
Si nous ajoutons une explicite NULLS LAST
Pour votre requête, alors il utilisera l'index comme prévu.
rds-9.6.5 root@db1=> explain select hour from performance order by hour desc NULLS LAST limit 10;
QUERY PLAN
-----------------------------------------------------------------------------------------------
Limit (cost=0.42..0.68 rows=10 width=8)
-> Index Only Scan using hour_idx on performance (cost=0.42..4334.37 rows=166530 width=8)
(2 rows)
Time: 0.526 ms
Alternativement, si nous laissons tomber le (non-défaut) NULLS LAST
À partir de votre index, la requête l'utilisera comme prévu sans modification.
rds-9.6.5 root@db1=> drop index hour_idx;
DROP INDEX
Time: 4.124 ms
rds-9.6.5 root@db1=> create index hour_idx
[more] - > on performance
[more] - > using btree
[more] - > (hour desc);
CREATE INDEX
Time: 69.220 ms
rds-9.6.5 root@db1=> explain select hour from performance order by hour desc limit 10;
QUERY PLAN
-----------------------------------------------------------------------------------------------
Limit (cost=0.42..0.68 rows=10 width=8)
-> Index Only Scan using hour_idx on performance (cost=0.42..4334.37 rows=166530 width=8)
(2 rows)
Time: 0.725 ms
Notez que vous pouvez également déposer le DESC
de votre index; PostgreSQL peut numériser des index avant et vers l'arrière et sur les index à colonne, il est généralement inutile de les inverser. Vous n'avez besoin que de faire attention à avoir la droite combinaison de l'ordre et de NULLS en premier/dernier.
rds-9.6.5 root@db1=> drop index hour_idx;
DROP INDEX
Time: 3.837 ms
rds-9.6.5 root@db1=> create index hour_idx
[more] - > on performance
[more] - > using btree
[more] - > (hour);
CREATE INDEX
Time: 94.815 ms
rds-9.6.5 root@db1=> explain select hour from performance order by hour desc limit 10;
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Limit (cost=0.42..0.68 rows=10 width=8)
-> Index Only Scan Backward using hour_idx on performance (cost=0.42..4334.37 rows=166530 width=8)
(2 rows)
Time: 0.740 ms
Si la plupart de vos questions ont l'intention de sélectionner des valeurs non nulles à partir de hour
, vous devriez envisager de construire un indice A partiel sur ces valeurs, c'est-à-dire quelque chose comme:
CREATE INDEX hour_not_null_idx ON performance (hour)
WHERE hour IS NOT NULL;
qui, tant que vous avez interrogé soit une valeur particulière de hour
, alors que Jeremy a démontré sa réponse ou ajoutez hour IS NOT NULL
à votre clause WHERE
, vous donnera les mêmes résultats, et éventuellement vous faire économiser un peu d'espace:
# explain select hour from performance where hour > now() order by hour desc limit 10;
Limit (cost=0.42..5.30 rows=10 width=8)
-> Index Only Scan Backward using hour_not_null_idx on performance (cost=0.42..8.72 rows=17 width=8)
Index Cond: (hour > now())
S'il n'y a pas de valeurs NULL
dans la colonne, vous devez le déclarer NOT NULL
(Je vais supposer que vous savez comment faire cela avec Alter Table; o)), puis créez l'index (sans NULLS LAST
, car ce n'est plus important de toute façon). Ensuite, vous obtenez le même avantage:
william=# create index hour_idx on performance using btree ( hour );
CREATE INDEX
william=# explain select hour from performance order by hour desc limit 10;
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Limit (cost=0.42..0.73 rows=10 width=8)
-> Index Only Scan Backward using hour_idx on performance (cost=0.42..5238.37 rows=166530 width=8)
(2 rows)