Vidéo pédagogique
Notice
Langue :
Français
Crédits
Serge Abiteboul (Intervention), Benjamin Nguyen (Intervention), Philippe Rigaux (Intervention)
Conditions d'utilisation
L'ensemble de ce contenu est mis à disposition sous licence CC BY-NC-ND 3.0 France https://creativecommons.org/licenses/by-nc-nd/3.0/fr/
DOI : 10.60527/mdk3-mz63
Citer cette ressource :
Serge Abiteboul, Benjamin Nguyen, Philippe Rigaux. Inria. (2015, 2 mars). Optimisation , in Exécution et optimisation. [Vidéo]. Canal-U. https://doi.org/10.60527/mdk3-mz63. (Consultée le 2 juin 2024)

Optimisation

Réalisation : 2 mars 2015 - Mise en ligne : 23 mai 2016
  • document 1 document 2 document 3
  • niveau 1 niveau 2 niveau 3
Thème
Documentation

Les applications, SQL et les SGBD

On peut utiliser SQL pour inspecter une base de données ou pour consulter quelques nuplets particuliers. Mais cela reste d’un intérêt limité. En pratique, SQL est surtout utilisé à travers une application comme langage d’accès aux données d’une ou plusieurs bases.

Comment se passe cette intégration de SQL dans un langage de programmation ? La méthode la plus simple consiste à recourir à une librairie de fonctions implantant une interface d’accès au SGBD. On parle d’API (Application Programming Interface), ou de pilote (« driver »). Voici un exemple concret avec l’API JDBC, une interface normalisée pour accéder aux bases relationnelles depuis une application java. On parcourt un ensemble d’utilisateurs et on regarde à quels MOOC ils sont inscrits. (La syntaxe a été légèrement simplifiée ; ne cherchez pas à compiler !)

ResultSet utils = executeQuery( "SELECT * FROM Utilisateur") ; while (utils.next()) { println ("Nom de l’utilisateur: " + utils.getString("nom")) ; // Cherchons ses MOOCs id_util = utils.getString("id")) ResultSet moocs = executeQuery( "SELECT * FROM Mooc where id_util= :id_util") ; while (moocs.next()) { // Affichage du mooc, etc. } } utils.close()

Ce petit bout de code devrait se comprendre tout seul. On reconnaît le motif d’accès vu et revu dans le cours et basé sur la notion d’itérateur :

  • on initialise dans la phase ‘open()’, ici ‘executeQuery(),
  • on itère sur le résultat dans la phase ‘next()’, ici ‘fetch()’, et finalement,
  • on libère les ressources avec close().

En fait, une interface de ce type permet au client de « piloter » l’exécution de la requête sur le serveur en dialoguant avec la racine du plan d’exécution. Si ce n’est pas clair, nous vous invitons à reprendre soigneusement les notions vues en cours.

Où est le problème ? En fait il y en a deux : l’optimisation et le décalage entre le langage de programmation et SQL.

L’ optimisation. Le premier problème est directement lié au sujet de cette semaine, l’optimisation. Toutes les données utilisées par l’application pourraient être obtenues par une seule jointure.

select * from Utilisateur as u, Mooc as m where u.id = m.id_util

L’utilisation de deux requêtes dans le code correspond à une certaine logique de l’application : on veut d’abord afficher le nom d’un utilisateur, puis la liste de ses MOOCs. Elle entraine un grave inconvénient : le système va devoir exécuter une fois la requête

select * from Utilisateur

puis exécuter la seconde autant de fois qu’il y a d’utilisateurs !

select * from Mooc where id_util = :id_util

Reportez-vous au cours (et à la réflexion qui en découle) pour vous convaincre que cette méthode d’accès est loin d’être optimale. Elle prive l’optimiseur de la possibilité de trouver le plan d’exécution approprié pour la jointure. En pratique, on peut obtenir une grave dégradation des temps de réponse. Le symptôme, identifié sous le nom des ‘1+n requêtes’ est connu et n’a, a priori, pas de solution évidente.

Que faire ? Nous reviendrons sur cette question après avoir considéré le second problème.

Les bases de données objets. D’un point de vue d’ingénierie logicielle, la cohabitation de deux langages basés sur des paradigmes totalement différents est peu satisfaisante. D’un côté, on a un langage (java ici) où tout est objet, et de l’autre un langage d’interrogation déclaratif (SQL) qui manipule des nuplets. Dans la pratique, les développeurs sont donc amenés à écrire répétitivement du code de conversion des nuplets en objets. Ce code est parfaitement stupide, lourd à maintenir, et coûteux en terme de la productivité.

C’est ce qui a conduit aux bases de données objets. Dans une base de données objets, les données sont représentées sous forme d'objets persistants. Un seul paradigme est donc utilisé dans le stockage, par le langage de programmation, et sur l'écran. Cela facilite énormément la programmation d'application et améliore la productivité du programmeur d'application, qui peut prendre en compte "le même objet du disque à l'écran". Cette technologie proposée dans le milieu des années 80 s'est relativement peu imposée. Elle est devenue plus populaire depuis une dizaine d'années avec des systèmes open source, très simples d'utilisation, comme db4O. On peut trouver de nombreuses raisons à ce semi-échec, demi-succès comme (i) les décideurs dans les années quatre-vingt, quatre-vingt dix étaient alors peu familiers avec les langages orientés-objets, et (ii) les systèmes relationnels offrent une large gamme de services que les systèmes de bases de données objets ne proposaient pas alors.

Le mapping objet-relationnel. Pour répondre à ce second problème (le problème des deux langages), les systèmes relationnels ont introduit une couche logicielle intermédiaire qui se charge automatiquement de la conversion entre le monde relationnel et le monde objet. Cette approche s’appelle Object Relational Mapping (ORM), ce qui dit bien ce que cela veut dire. L'approche ORM est une adaptation approximative et relativement inélégante des bases de données objet. Voici la même fonctionnalité que précédemment, mais écrite en ORM (ici, Hibernate, un outil ORM très populaire). Comme précédemment, on a respecté la syntaxe mais on l'a quelque peu dégraissée  pour montrer l’essentiel.

List<Utilisateurs> utilisateurs = createQuery("from Utilisateur"); for (Utilisateur util : utilisateurs) { println (`Nom : ` + util.getNom()) ; for (Mooc m : util.getMoocs()) { // du Mooc } }

Dans ce code, tout est objet java, les tables sont vues comme des ensembles d’instances de classes Utilisateur ou Mooc. De plus, la couche ORM implante une navigation d’un objet à l’autre (par exemple la fonction getMoocs() associée à un utilisateur). Notre second problème est résolu : nous avons une couche d’accès aux données qui est totalement homogène, et on ne voit même plus de SQL. On poursuit en fait une démarche d’abstraction qui nous a mené des fichiers vers les bases de données, puis de ces dernières vers les langages de programmation objet.

Retour sur le premier problème. En quoi notre premier problème, celui d’une répétition non optimale de requêtes SQL élémentaires, peut-il lui aussi être résolu ? Et bien, la couche d’abstraction ORM va nous permettre d’indiquer, de manière déclarative, que les utilisateurs et leurs MOOCs doivent être récupérés ensemble, solidairement, par une jointure SQL. Nous ne montrons pas ici comment on effectue cette spécification car cela nous emmènerait trop loin, mais il est amusant de constater que les langages de requêtes pour les systèmes ORM les plus avancés (comme le langage HQL intégré à Hibernate) réinvente (de façon partielle et maladroite) des aspects de OQL, le langage déclaratif qui a été proposé comme standard pour les bases de données objet.

Ce qu’il faut retenir :

  1. Une approche naïve de l’intégration de SQL à une application résulte en une utilisation non optimale du SGBD et notamment de son optimiseur. (On peut d’autant mieux apprécier ce défaut que l’on a suivi le cours sur l’optimisation bien sûr !)
  2. Nous disposons, dans les langages objets, d’API qui vont faciliter la programmation d’applications.
  3. Nous disposons également de couches applicatives spécialisées qui vont produire les requêtes SQL pour nous et s’assurer que les possibilités d’optimisation du SGBD sont bel et bien exploitées.

Il n’ en reste pas moins que l’ajout d’une couche supplémentaire dans la pile des niveaux d’abstractions entre l’utilisateur et les données est une source réelle de complexification des architectures applicatives, avec les inconvénients (performance notamment) qui en découlent. Le succès des solutions de type Hibernate (et autres, équivalentes, intégrées aux frameworks de développement) montre que le choix est bien souvent de privilégier la clarté du code applicatif sur la cohabitation du requêtes SQL et d’un langage objet.

Pour en savoir plus :

Dans la même collection

Avec les mêmes intervenants et intervenantes