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 :
-
Sauvegarde système au niveau des fichiers;
- sauvegarde PostgreSQL : dump.
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.
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:
-
Les requêtes générées ne sont pas toujours bien organisées: cela
peut poser un problème s'il y a des dépendances circulaires dans le
schéma;
- par défaut, les blobs ne font pas partie du dump. Il faut
explicitement utiliser l'option -b et un format de sortie
de type tar ou customisé (options -F t ou
-F c).
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:
-
Les contraintes déclaratives
- 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:
-
Contraintes de domaine: limitent les valeurs qui peuvent être
prises par un attribut (not null, unique, default, checke.g. check(latitude between -90 and 90)).
- Contraintes d'intégrité d'entité: précisent la clé primaire
(e.g. primary key (NUMERO_STATION)).
- Contraintes d'intégrité référentielle qui assurent la cohérence
des relations clé primaire - clé étrangère correspondante (e.g.
alter table STATION
add foreign key (CODE_NUM_PAYS)
references PAYS (CODE_NUM_PAYS);
- Les assertions, qui imposent des contraintes plus générales
entre attributs quelconques, s'utilisent comme quand elles ne sont
pas rattachées à une table en particulier, par exemple:
create assertion pasdeventeaperte check (
not exists (select * from
ventes where prixvente <
(select prixachat from articles where
articles.prodid = ventes.prodid)));
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
Où 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é.
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
-
http://postgresql.org
- http://phd.pp.ru/Software/SQL/PostgreSQL-vs-MySQL.html
- http://www.geocities.com/mailsoftware42/db/dbs.html
- http://www.linux-france.org/article/serveur/psql/