Explications

Fichier CSV des départements français (95 lignes)

Extrait des premières lignes du fichier  :

1
numero,nom,pop
2
1,Ain,529
3
2,Aisne,552
4
3,Allier,357
5
4,Alpes-de-Haute-Provence,145
6
5,Hautes-Alpes,127
7
6,Alpes-Maritimes,1023
8
7,Ardèche,295
9
8,Ardennes,299
10
9,Ariège,143

Fichier CSV des villes françaises (36700 lignes)

Extrait des premières lignes du fichier :

1
codeinsee,departement,nom,pop2010,pop1999
2
1284,1,Ozan,618,469
3
1123,1,Cormoranche-sur-Saône,1058,903
4
1298,1,Plagne,129,83
5
1422,1,Tossiat,1406,1111
6
1309,1,Pouillat,88,58
7
1421,1,Torcieu,698,643
8
1320,1,Replonges,3500,2841
9
1119,1,Corcelles,243,222
10
1288,1,Péron,2143,1578

Question

Instancier la base de données Postgres permettant de gérer ces fichiers.

Solution

1
CREATE TABLE dpt (
2
numero CHAR(2) PRIMARY KEY,
3
nom VARCHAR,
4
pop INTEGER
5
);
6
7
CREATE TABLE ville (
8
codeinsee CHAR(5) PRIMARY KEY,
9
departement CHAR(3),
10
nom VARCHAR,
11
pop2010 INTEGER,
12
pop1999 INTEGER
13
);
1
\copy dpt FROM dpt_france.csv WITH CSV HEADER DELIMITER ','
2
3
\copy ville FROM villes_france.csv WITH CSV HEADER DELIMITER ','
Remarque

Le fichier des départements n'est pas complet, la contrainte de clé étrangère ne peut pas être déclarée ici.

VACUUM

Exécutez la commande ci-après, on observe seulement 21300 lignes collectées, là ou 36700 étaient attendues.

1
EXPLAIN
2
SELECT * FROM ville;
1
                        QUERY PLAN
2
-----------------------------------------------------------
3
 Seq Scan on ville  (cost=0.00..497.00 rows=21300 width=32)

Question

Expliquez pourquoi et proposez une solution.

Solution

Il faut exécuter la commande VACUUM ANALYZE pour commander au moteur de collecter des statistiques sur les données fraîchement entrées. Le résultat de la requête est alors celui attendu.

1
VACUUM ANALYSE;
2
EXPLAIN
3
SELECT * FROM ville;
1
                       QUERY PLAN                         
2
-----------------------------------------------------------
3
 Seq Scan on ville  (cost=0.00..651.00 rows=36700 width=30)

Projection

Exécutez la commande ci-après permettant de projeter le nom des villes.

1
EXPLAIN
2
SELECT nom FROM ville;
1
                        QUERY PLAN
2
-----------------------------------------------------------
3
 Seq Scan on ville  (cost=0.00..651.00 rows=36700 width=12)

Question

Quelle différence observez-vous avec le plan de la requête SELECT * FROM ville ? Expliquez.

Solution

La seule différence concerne la valeur de width qui passe de 30 à 12. Cette valeur indique la largeur moyenne (en octet) d'une ligne. La projection a logiquement réduit cette valeur.

Restriction et tri

Exécutez les commande ci-après permettant respectivement de faire une restriction et un tri sur le nom des villes.

1
EXPLAIN
2
SELECT nom FROM ville
3
WHERE nom='Compiègne';
1
                      QUERY PLAN                       
2
-------------------------------------------------------
3
 Seq Scan on ville  (cost=0.00..742.75 rows=1 width=12)
4
   Filter: ((nom)::text = 'Compiègne'::text)
1
EXPLAIN
2
SELECT nom FROM ville
3
ORDER BY nom;
1
                           QUERY PLAN                            
2
-----------------------------------------------------------------
3
 Sort  (cost=3433.50..3525.25 rows=36700 width=12)
4
   Sort Key: nom
5
   ->  Seq Scan on ville  (cost=0.00..651.00 rows=36700 width=12)

Question

Qu'observez-vous ? Proposez une solution pour améliorer ces deux requêtes. Mesurez le gain apporté par la solution. Notez les éventuels inconvénient apportés par la solution.

Solution

Les coûts sont augmenté car il faut faire des recherches dans le champ nom. On peut optimiser ces requêtes en indexant ce champ.

1
CREATE INDEX idxnom ON ville(nom);
1
EXPLAIN
2
SELECT nom FROM ville
3
WHERE nom='Compiègne';
1
                               QUERY PLAN                                
2
-------------------------------------------------------------------------
3
 Index Only Scan using idxnom on ville  (cost=0.29..4.31 rows=1 width=12)
4
   Index Cond: (nom = 'Compiègne'::text)
  • Le coût dans le pire cas a été réduit de 742 à 4,3 (gain d'un facteur 172).

  • On notera que le coût minimum, dans le cas où Compiègne est la première ville de la table est augmenté de 0 à 0,29 (c'est le coût fixe de l'accès à l'index). C'est ici une perte négligeable par rapport au gain dans le pire cas et qui ne se produira que dans des cas très rares.

  • On ne mesure pas ici les autres inconvénients de l'indexation (temps de mise à jour, espace disque complémentaire).

1
EXPLAIN
2
SELECT nom FROM ville
3
ORDER BY nom;
1
                                   QUERY PLAN                                   
2
--------------------------------------------------------------------------------
3
 Index Only Scan using idxnom on ville  (cost=0.29..1130.79 rows=36700 width=12)
  • Le coût pour récupérer la première ligne passe de 3433 à 0,29 (qui est le coût de l'accès à l'index). Il y a donc un gain très important, qui est intéressant si l'on souhaite transmettre les noms des villes progressivement et dans l'ordre du tri.

  • Le coût pour récupérer toutes les lignes passent de 3525 à 1130 (gain d'un facteur 3,1).

Jointure

Exécutez la commande ci-après permettant de faire une jointure.

1
EXPLAIN
2
SELECT v.nom, d.nom
3
FROM ville v JOIN dpt d
4
ON v.departement = d.numero;
1
Hash Join  (cost=3.14..1136.96 rows=34520 width=22)
2
   Hash Cond: (v.departement = d.numero)
3
   ->  Seq Scan on ville v  (cost=0.00..651.00 rows=36700 width=16)
4
   ->  Hash  (cost=1.95..1.95 rows=95 width=13)
5
         ->  Seq Scan on dpt d  (cost=0.00..1.95 rows=95 width=13)

Question

Indexez la clé étrangère v.departement. Observez-vous pas de gain ? Expliquez.

Solution

1
CREATE INDEX idxdpt ON ville(departement);

Le plan d'exécution n'a pas changé du tout. Le hash join utilisé pour effectuer la jointure n'utilise pas l'index.

Question

À partir des plans précédents, calculez le gain qu'apporterait une dénormalisation à cette jointure.

Solution

Le plan serait similaire à celui de SELECT nom FROM ville (avec simplement un width plus important).

Le coût serait donc de 651 au lieu de 1136 : la requête serait 1,75 fois plus rapide.