Précédent Remonter Suivant

Chapitre 4  PostgreSQL

4.1  Introduction

4.1.1  PostgreSQL vs MySQL 

Dans le cadre de cette formation, PostgreSQL a été choisi comme SGBD de par ses performances au niveau transactionnel et capacités à gérer le verrouillage de données par versions (MVCC multi-version concurrency control). La version 4.0 de MySQL (en version bêta en novembre 2002) incorpore en standard le moteur InnoDB, jusqu'ici réservé à la version (payante) Pro et atteint des capacités similaires au niveau transactionnel et de contrôle d'accès aux données. En revanche, les requêtes imbriquées ne sont prévues que pour la version 4.1 de MySQL .

A l'heure actuelle, avec les versions de production, on peut se fier au tableau comparatif suivant;
  PostgreSQL  MySQL 
SQL standard PostgreSQL comprend un bon sous-ensemble de SQL92 et même un bon surensemble; PostgreSQL dispose de capacités d'héritage objet (outer joins existent) MySQL comprend un sous-ensemble de SQL92 mais il manque notamment de sous-requêtes et de vues.
VITESSE PostgreSQL est relativement lent, mais avec beaucoup d'options d'optimisation. De l'option -F pour réécriture de requêtes jusqu'aux différentes stratégies d'optimisationSET KSQO, SET GEQO). PostgreSQL créé un nouveau processus à chaque connexion (ralenti en Solaris/Windows mais pas en linux) MySQL est très rapide avec des requêtes simples, mais beaucoup moins avec des complexes. Les connexions sont gérés très rapidement.
STABILITE PostgreSQL à très bonne stabilité à partir de la version 7.1 MySQL est très utilisé et très stable.

  PostgreSQL  MySQL 
INTEGRITE PostgreSQL a des transactions/rollbacks et clés étrangères MySQL 4.0bêta gère les transactions et les clés étrangères.
TRIGGERS /
PROCEDURES STOCKEES PostgreSQL peut être étendu avec des fonction écrites en C, pgsql, python, perl et tcl . MySQL ne gère pas tout cela, juste des librairies partagées pour fonctions C.z
VEROUILLAGE PostgreSQL utilise MVCC (MultiVersion Concurrency Control) MySQL est censé faire la même chose dans sa version 4.0; jusqu'alors, seulement verouillage de tables.
BLOBS En PostgreSQL , les blobs sont spéciaux; ils se gèrent dans une table à part et ils sont disjoints des tables normales En MySQL c'est un type comme un autre, malgré quelques limitations.

4.2  Objets binaires

Le stockage d'objets binaires de grande taille (Binary Large OBjects ou BLOBS) dans les bases de données est souvent d'utilité. Malheureusement, le blob n'est pas un type accepté par le standard SQL et sa mise en oeuvre diffère d'un SGBD à un autre. En PostgreSQL leur manipulation est particulièrement spéciale (alors qu'en MySQL ils se comportent comme d'autres types standard).

Jusqu'à la version 7.1 de PostgreSQL , la taille de n'importe quel tuple d'une base de données ne pouvait excéder 8192 octets. Avec la version 7.1, le mécanisme appelé TOAST permet le stockage de données binaires de taille arbitraire, en les stockant dans la table pg_largeobject, en les découpant en morceaux de 8192 octets.

4.2.1  Import/export de blobs

L'importation de blobs se fait avec la fonction lo_import. Elle prend comme argument la connexion et le nom du fichier à utiliser comme source. La fonction lo_export s'utilise de façon similaire. Ci dessous, un exemple d'utilisation de blobs à partir de la ligne de commande, avec psql, mais l'équivalent et aisément faisable avec les fonctions correspondantes en PHP (cf. section 4.2.2).
CREATE TABLE isohyetes (
    code_num_pays   numeric(3),
    annee           numeric(4),
    mois            numeric(2),
    image           oid
);
INSERT INTO isohyetes (code_num_pays, annee,mois,image)
    VALUES (33,2002,11, lo_import('/etc/motd'));
SELECT lo_export(isohyetes.image, '/tmp/motd') FROM isohyetes
    WHERE annee = 2002 and mois=11 and code_num_pays=33;
En fait, le type oid est simplement un entier qui indique l'indice utilisé pour stocker l'image en question dans la table pg_largeobject.

Pour effacer l'image précédente on pourrait faire:
select lo_unlink((select image from isohyetes 
    where  annee = 2002 and
           mois=11 and code_num_pays=33));
delete from isohyetes where annee = 2002 and
           mois=11 and code_num_pays=33;

4.2.2  Blobs en PHP

Les fonctions d'accès aux blobs PostgreSQL existent également en PHP. Elles se préfixent pg_ et permettent d'importer/exporter des fichiers, créer, lire, et effacer des objets binaires. L'exemple précédent s'écrirait en PHP de façon similaire:
<?php
$database = pg_connect ("dbname=cpc");
// pg_exec ($database, "begin");
$req="INSERT INTO isohyetes (code_num_pays, annee,mois,image)
     VALUES (33,2002,11, lo_import('/etc/motd'))"; 
if (!pg_exec ($database, $req))
     print pg_errormessage();
else {
  print ("ok<br>");
  $req="SELECT lo_export(isohyetes.image, '/tmp/motd') FROM isohyetes
    WHERE annee = 2002 and mois=11 and code_num_pays=33";
  if (!pg_exec ($database, $req))
     print pg_errormessage();
  else
    print "ok<br>";
}
 ?>
Les fonctions d'accès aux blobs (pg_loopen, pg_loread, pg_loreadall,...) doivent cependant avoir lieu à l'intérieur d'une transaction. Exception notable, les fonctions pg_loimport et pg_loexport n'ont pas besoin de cela.

Voici un autre exemple:
<?php
$database = pg_connect ("dbname=form1");
pg_exec ($database, "begin");
$oid = pg_locreate ($database);
echo ("$oid\n");
$handle = pg_loopen ($database, $oid, "w");
echo ("$handle\n");
pg_lowrite ($handle, "gaga");
pg_loclose ($handle);
pg_exec ($database, "commit");
 ?>

4.3  Sauvegarde

Il y a deux stratégies différentes pour faires des sauvegardes sur PostgreSQL : La première stratégie n'est utilisable que si la base de données est arrêtée. Dans le cas contraire, toute activité sera recopiée dans l'état, faisant que la base de données reste possiblement dans un état incohérent. On parlera plutôt du dump.

4.3.1  Dump

Pour faire une sauvegarde, on utilise la commande pg_dump ou pg_dumpall.

pg_dump

pg_dump est un utilitaire pour extraire une base PostgreSQL vers un fichier script contenant des commandes de requêtes. Les fichiers script sont en format texte et peuvent être utilisés pour reconstruire la base même sur d'autres machines et d'autres architectures, avec quelques modifications même sur d'autres produits SGBDR. De plus, il existe des alternatives dans les formats de fichiers archive comme indiqué avec l'utilisation de pg_restore pour reconstruire la base, qui admettent aussi pg_restore pour sélectionner ce qui doit être restauré, ou même pour retrier les priorités d'items à restaurer. Les fichiers archive sont prévus pour être portables sur diverses architectures.

pg_dump produira les requêtes nécessaires pour régénérer tous les types utilisateurs, fonctions, tables, index, agrégats, et opérateurs. De plus, toutes les données sont copiées en format texte et ainsi facilement copiables à nouveau, aussi bien qu'importables dans des éditeurs.

pg_dump fait des sauvegardes cohérentes même si la base est utilisée concurremment; elle ne bloque pas les autres utilisateurs qui accèdent à la base (en lecture ou écriture). Elle a deux limitations cependant:

pg_dumpall

pg_dumpall est très similaire à pg_dump; elle sauvegarde toutes les bases contenues dans un serveur, y compris les utilisateurs et les groupes. Il faut avoir les droits d'administrateur pour le faire. Cette commande à les mêmes limitations que pg_dump.

4.3.2  Restauration

Si la sauvegarde a été faite en créant un script SQL (par défaut), on peut tout simplement faire la restauration de la base avec psql :

psql base < fichierscript.sql

Si l'on a utilisé l'un des autres formats, on peut utiliser l'application pg_restore (tar ou customisé).

pg_restore

pg_restore est un utilitaire pour restaurer une base PostgreSQL sepuis une archive créée par pg_dump dans un des formats non texte.

Il générera les commandes nécessaires pour régénérer tous les types utilisateurs, fonctions, tables, indexes, agrégats, et opérateurs, aussi bien que les données dans les tables. Les fichiers archives contiennent l'information pour pg_restore pour reconstruire la base, mais aussi permet à pg_restore de sélectionner ce qui sera restauré, ou même pour réordonnancer les items avant d'être restaurés. Les fichiers archive seront modélisés pour pouvoir être portables sur diverses architectures. pg_restore peut opérer en deux modes : si un nom de base est spécifié, l'archive est restaurée directement dans la base. Sinon, un script contenant les commandes SQL nécesasires pour reconstruire la base est créé (et écrit dans un fichier ou une sortie standard), similaire à ceux créés par pg_dump en format texte. Certaines des options contrôlant la sortie script sont donc analogues aux options de pg_dump. Évidemment, pg_restore ne peut restaurer l'information qui n'est pas présente dans le fichier archive; par exemple, si l'archive est produite en utilisant l'option "sauvegarde des données comme INSERT", pg_restore ne sera pas capable de charger les données utilisant les instructions COPY.

4.4  Contraintes, triggers et procédures stockées

Un des avantages principaux d'utiliser un SGBDR pour stocker des données, est la facilité avec laquelle on peut garantir la cohérence des données. Deux types de mécanismes existent pour cela:
  1. Les contraintes déclaratives
  2. Les triggers (standard à partir de SQL/99) et procédures stockées (non-standard).

4.4.1  Les contraintes declaratives

Parmi les contraintes déclaratives on trouve: Il est important de noter que pour éviter des problèmes de dépendance réciproque, on peut faire de sorte que les contraintes ne soient vérifiées qu'à la fin de chaque transaction et non pas à chaque instruction SQL. C'est le mode deferred spécifié avec l'instruction set constraints liste|all deferred | immediate.

4.4.2  Les triggers

Les triggers sont des procédures compilées, cataloguées dans le dictionnaire, et qui s'exécutent automatiquement lorsqu'un événement spécifié se produit.

La syntaxe est:
     CREATE TRIGGER nom_du_trigger
     evenement ON table
     WHEN (condition)
     instructions
     FOR REACH ROW | STATEMENT
evenement est l'une de before update, after update, before insert, after insert, before delete, after delete. La condition restreint l'application du trigger et for each row indique que les instructions doivent s'appliquer à chaque insertion/modification/suppression d'un tuple, alors que for each statement ne le fait qu'une fois par evenement declenchant.

Par exemple, si l'on veut faire que le releve_mensuel des stations pluviométriques se tienne à jour automatiquement à chaque introduction d'une donnée journalière, on pourrait créer un trigger pour mettre à jour cette table. Un exemple plus simple est la mise à jour systématique de la table releve_quotidien, pour s'assurer que les champs numero_annee et numero_mois sont cohérents avec le champ date_releve; c'est l'exemple 4.4.2.

     create trigger coherencedatereleve
        after insert on releve_quotidien
          for each row(
         update  releve_quotidien set numero_annee = 
               date_part('year',date_releve);
         update  releve_quotidien set numero_mois = 
               date_part('month',
        date_releve));
Malheureusement, cet exemple ne marche pas en PostgreSQL car un trigger requiert l'utilisation d'une procédure stockée de type de retour opaque.

4.4.3  Les procédures stockées

Une procédure stockée est un ensemble d'instructions SQL précompilées, qui peuvent être exécutées sur demande. A la différence d'un trigger, il n'y a pas d'événement déclencheur. La procédure doit est exécutée sur demande, souvent en utilisant l'instruction execute.

Sur PostgreSQL, les triggers ne peuvent être utilisés que pour appeler une procédure stockée. L'exemple 4.4.2 de la section précédente doit donc être écrit comme sur l'exemple 4.4.3.

     CREATE FUNCTION miseajour () RETURNS opaque AS '
       BEGIN
         update  releve_quotidien set numero_annee = 
              extract(YEAR FROM date_releve);
         update  releve_quotidien set numero_mois = 
              extract(MONTH FROM date_releve);
         return NEW;
       END;
     ' LANGUAGE 'plpgsql';
     create trigger coherencedatereleve
        after insert on releve_quotidien
          for each row
            execute procedure miseajour();
Pour les effacer:

drop trigger coherencedatereleve on releve_quotidien;
drop function miseajour();

4.5  Optimisation

La règle du 20-80 de Vilfredo Pareto (1848-1923), un économiste italien, indique que 20% de quelque chose représente souvent 80% du coût total d'une autre. Pour les bases de données, ce sont les requêtes qui dépensent la plupart du temps du serveur. Ces quelques remarques indiqueront comment les optimiser.

4.5.1  Index de petites tables

Les index sont un mécanisme essentiel pour optimiser la réactivité d'une base de données. Sans eux, on devrait toujours parcourir les tables pour obtenir l'information recherchée. Cependant, ils ne sont pas toujours utiles. En effet, lorsque les tables sont trop petites la manière la plus performante d'acceder à l'information d'une table est simplement de la lire complètement. Sans index, le SGBD ne se pose pas la question: il la lit complètement, est c'est la façon la plus efficace de le faire. Avec un index, le SGBD évalue le coût d'utilisation des index par rapport à celui de parcours de la table. Conclusion, pour optimiser de requêtes qui utilisent des petites tables, faites un drop de l'indice de la table.

4.5.2  Index de grosses tables

Pour les grosses tables, les indices sont une aubaine. Pour optimiser les requêtes, il faut souvent créer des indices supplémentaires dans les champs qui sont des clés étrangères à autres tables.

Par exemple, pour une clause where qui met en rapport les tables A et B:

A.cle = B.cle_etr

il faut s'assurer que B.cle_etr est indexé.

4.5.3  Explain

Pour des requêtes qui posent problème et dont les indices évidents existent, il faut regarder de près ce que fait vraiment le SGBD pour voir comment l'optimiser. Pour cela existe la commande explain.

Pour l'utiliser il suffit de la placer avant la requête à analyser:
mabd=# EXPLAIN SELECT t12_bskid, t12_pnb, t12_lne, t12_tck
mabd-# FROM t12
mabd-# WHERE t12_bskid >= 35
mabd-# ORDER BY t12_bskid, t12_pnb, t12_tck, t12_lne;
Sort  (cost=1348.70..1348.70 rows=8565 width=16)
  ->  Seq Scan on t12  (cost=0.00..789.20 rows=8565 width=16)
Les tris sequentiels sont utilisés pour satisfaire la clause order by. En créant un indice sur les champs utilisés pour trier, ça peut aller beaucoup plus vite:
mabd=# create index t12_bskid_pnb_tck_lne 
       on t12 (t12_bskid, t12_pnb, t12_tck, t12_lne);
CREATE
En refaisant tourner explain on regarde l'amélioration:
mabd=# EXPLAIN SELECT t12_bskid, t12_pnb, t12_lne, t12_tck
mabd-# FROM t12
mabd-# WHERE t12_bskid >= 35
mabd-# ORDER BY t12_bskid, t12_pnb, t12_tck, t12_lne;
NOTICE:  QUERY PLAN:
Sort  (cost=1348.70..1348.70 rows=8565 width=16)
  ->  Seq Scan on t12  (cost=0.00..789.20 rows=8565 width=16)
Ca ne s'est pas vraiment arrangé ! C'est parce que l'optimiseur n'a pas utilisé le nouvel indice. Pour cela, il faut lui dire de refaire l'analyse de la table:
mabd=# vacuum analyze t12;
VACUUM
Et l'on regarde une nouvelle fois les performances:
\begin{verbatim}
mabd=# EXPLAIN SELECT t12_bskid, t12_pnb, t12_lne, t12_tck
mabd-# FROM t12
mabd-# WHERE t12_bskid >= 35
mabd-# ORDER BY t12_bskid, t12_pnb, t12_tck, t12_lne;
NOTICE:  QUERY PLAN:
Index Scan using t12_bskid_pnb_tck_lne on t12  
(cost=0.00..2232.11 rows=25693 width=16)
Cette fois, on voit que l'indice est bien utilisé, et que le coût et bien moindre.

Cependant, PostgreSQL n'utilise pas toujours les indices, même s'ils existent. Pour forcer son utilisation, on peut utiliser la variable ENABLE_SEQSCAN:
mabd=# set enable_seqscan='false';
SET VARIABLE
Ainsi, on peut évaluer le coût d'une requête en étant sûr que tous les indices seront utilisés. Pour revenir à l'état usuel, on peut faire:
mabd=# set enable_seqscan='true';
SET VARIABLE

4.6  Sécurité

4.6.1  Fonctions et triggers

Les fonctions et les triggers permettent aux utilisateurs d'introduire des fonctions qui seront éxécutées par d'autres utilisateurs sans le savoir - créant ainsi des chevaux de Troie. Pour contrôler ce problème potentiel, il faut regarder les privilèges de création de triggers sur les tables (version 7.2).

Un audit de ce qu'a été fait est possible et il souhaitable de contrôler les catalogues système pg_class (informations sur les tables, y compris le propriétaire), pg_shadow (contient les mots de passe utilisateurs) et pg_group (informations sur les groupes auxquels appartient un utilisateur) sont également possible.

Des fonctions écrites en d'autres langages que SQL sont exécutées avec les droits du serveur de base de données; ainsi, il est possible de changer les structures internes du serveur et passer outre tout autre système de contrôle. Ceci est un problème inhérent à l'utilisation d'extensions en langage compilé.

4.6.2  Privilèges

psql dispose de la commande \z pour regarder les privilèges sur une table.

Limitation PostgreSQL : on ne peut pas donner les privilèges par colonne. Pour ce faire, il faut créer une vue, et travailler avec. Autrement, ça marche bien, avec des groupes et tout et tout.

La nouvelle version 7.3 (en bêta en novembre 2002) comporte un système complet de privilèges.

4.7  Références

  1. http://postgresql.org
  2. http://phd.pp.ru/Software/SQL/PostgreSQL-vs-MySQL.html
  3. http://www.geocities.com/mailsoftware42/db/dbs.html
  4. http://www.linux-france.org/article/serveur/psql/

Précédent Remonter Suivant