Structure d’application visant à améliorer les performances de la base de données

Ne modifiez pas la propriété text d’un objet SQLStatement après son exécution. Utilisez plutôt une occurrence de SQLStatement pour chaque instruction SQL et définissez des valeurs différentes par le biais de paramètres d’instruction.

Avant d’exécuter une instruction SQL, l’environnement d’exécution la prépare (la compile) pour déterminer les étapes effectuées en interne pour l’exécuter. Lorsque vous appelez SQLStatement.execute() sur une occurrence de SQLStatement qui n’a encore jamais été exécutée, l’instruction est automatiquement préparée avant son exécution. Lors des prochains appels à la méthode execute() , et tant que la propriété SQLStatement.text ne change pas, l’instruction est déjà préparée. Son exécution est donc plus rapide.

Pour optimiser au maximum la réutilisation d’une instruction, si des valeurs doivent être modifiées entre ses exécutions, personnalisez-la à l’aide de paramètres d’instruction (qui sont spécifiés à l’aide de la propriété de tableau associatif SQLStatement.parameters ) . Contrairement à la modification de la propriété text de l’occurrence de SQLStatement, lorsque vous modifiez les valeurs des paramètres d’instruction, le moteur d’exécution n’a pas besoin de préparer à nouveau l’instruction.

Lorsque vous réutilisez une occurrence de SQLStatement, l’application doit conserver une référence à cette occurrence une fois celle-ci préparée. A cet effet, déclarez la variable en tant que variable de domaine de classe et non en tant que variable de domaine de fonction. Pour ce faire, structurez l’application de sorte que l’instruction SQL soit enveloppée dans une seule classe. Un groupe d’instructions exécutées en combinaison peut également être enveloppé dans une même classe (cette technique repose sur l’utilisation du patron de conception Commande). Définies en tant que variables de membre de la classe, les occurrences persistent tant que l’occurrence de la classe enveloppe existe dans l’application. Au minimum, vous pouvez simplement définir une variable contenant l’occurrence de SQLStatement à l’extérieur d’une fonction de sorte que cette occurrence reste en mémoire. Par exemple, déclarez l’occurrence de SQLStatement en tant que variable de membre d’une classe ActionScript ou en tant que variable autre qu’une fonction dans un fichier JavaScript. Vous pouvez ensuite définir les valeurs de paramètres de l’instruction et appeler sa méthode execute() lorsque vous souhaitez véritablement exécuter la requête.

Utilisez des index de base de données pour accélérer l’exécution des opérations de comparaison et de tri.

Lorsque vous créez un index pour une colonne, la base de données enregistre une copie des données de celle-ci. Les données sont triées par ordre numérique ou alphabétique. La base de données est ainsi en mesure de faire correspondre des données (utilisation de l’opérateur d’égalité, par exemple) et de trier les résultats à l’aide de la clause ORDER BY , et ce, rapidement.

Les index de base de données sont toujours tenus à jour, ce qui entraîne un léger ralentissement des opérations de modification des données (INSERT ou UPDATE) effectuées sur cette table. L’augmentation de la vitesse de récupération des données peut cependant être significative. En raison de ce compromis, évitez tout simplement d’indexer toutes les colonnes d’une table. Adoptez plutôt une stratégie de définition des index. Suivez les directives ci-dessous pour planifier votre stratégie d’indexation :

  • Indexez les colonnes utilisées pour la jointure des tables, dans les clauses WHERE ou ORDER BY.

  • Si vous utilisez fréquemment des colonnes ensemble, placez-les dans un même index.

  • Spécifiez le classement COLLATE NOCASE pour l’index d’une colonne contenant des données texte que vous récupérez en ordre alphabétique.

Envisagez de pré-compiler les instructions SQL pendant les périodes d’inactivité de l’application.

Lors de sa première exécution, une instruction SQL est plus lente, car le texte SQL est préparé (compilé) par le moteur de base de données. Comme la préparation et l’exécution d’une instruction peut être une opération exigeante, il est judicieux de précharger les données initiales, puis d’exécuter les autres instructions en arrière-plan :

  1. Chargez les données dont l’application a d’abord besoin..

  2. Exécutez les autres instructions au terme des opérations de démarrage initial de l’application ou lors d’une autre période « d’inactivité » de celle-ci.

Supposons, par exemple, que l’application n’accède pas du tout à la base de données lors de l’affichage de son écran initial. Attendez donc que cet écran soit affiché avant d’établir la connexion à la base de données. Enfin, créez les occurrences de SQLStatement et exécutez toutes celles qui sont disponibles.

A l’inverse, supposons que l’application affiche immédiatement certaines données à son démarrage, par exemple le résultat d’une requête particulière. Dans ce cas, continuez et exécutez l’occurrence de SQLStatement pour cette requête. Dès que les données initiales sont chargées et affichées, créez des occurrences de SQLStatement pour les autres opérations de base de données et, si possible, exécutez ultérieurement les autres instructions nécessaires.

En pratique, si vous réutilisez des occurrences de SQLStatement, le système ne doit consacrer du temps supplémentaire à la préparation de l’instruction qu’une seule fois. L’impact sur les performances globales est probablement mineur.

Groupez plusieurs opérations de modification des données SQL dans une même transaction.

Supposons que vous exécutiez un grand nombre d’instructions SQL impliquant l’ajout ou la modification de données (instructions INSERT ou UPDATE ). Vous pouvez augmenter significativement les performances en exécutant toutes les instructions dans une transaction explicite. Si vous ne commencez pas une transaction de façon explicite, chacune des instructions s’exécute dans sa propre transaction créée automatiquement. A l’issue de l’exécution de chaque transaction (chaque instruction), le moteur d’exécution écrit les données résultantes dans le fichier de la base de données sur disque.

Regardez à présent ce qu’il se passe si vous créez explicitement une transaction et exécutez les instructions dans le contexte de cette transaction. L’environnement d’exécution effectue toutes les modifications en mémoire, puis écrit simultanément toutes les modifications dans le fichier de la base de données lorsque la transaction est validée. L’écriture de données sur le disque est généralement la partie la plus longue de l’opération. Par conséquent, écrire sur le disque une seule fois plutôt qu’une fois par instruction SQL peut améliorer significativement les performances.

Lorsque les résultats de la requête SELECT sont volumineux, traitez-les par blocs à l’aide des méthodes execute() (avec le paramètre prefetch ) et next() de la classe SQLStatement.

Supposons que vous exécutiez une instruction SQL qui extrait un jeu de résultats volumineux. L’application traite ensuite chaque ligne de données dans une boucle. Elle formate les données ou s’en sert pour créer des objets, par exemple. Le traitement des données est susceptible d’être long, ce qui peut entraîner des problèmes de rendu (écran bloqué ou non réactif, par exemple). Pour parer à ces écueils, vous pouvez diviser les tâches par blocs (voir Opérations asynchrones ). L’API de la base de données SQL facilite la division du traitement des données.

La méthode execute() de la classe SQLStatement possède un paramètre facultatif, prefetch (le premier). Si vous le définissez, il spécifie le nombre de lignes de résultats que renvoie la base de données au terme de l’exécution :

dbStatement.addEventListener(SQLEvent.RESULT, resultHandler); 
dbStatement.execute(100); // 100 rows maximum returned in the first set

Une fois le premier jeu de résultats renvoyé, vous pouvez appeler la méthode next() pour poursuivre l’exécution de l’instruction et extraire un autre jeu de lignes de résultat. A l’instar de la méthode execute() , la méthode next() gère le paramètre prefetch , qui permet de spécifier le nombre maximal de lignes à renvoyer :

// This method is called when the execute() or next() method completes 
function resultHandler(event:SQLEvent):void 
{ 
    var result:SQLResult = dbStatement.getResult(); 
    if (result != null) 
    { 
        var numRows:int = result.data.length; 
        for (var i:int = 0; i < numRows; i++) 
        { 
            // Process the result data 
        } 
         
        if (!result.complete) 
        { 
            dbStatement.next(100); 
        } 
    } 
}

Vous pouvez continuer d’appeler la méthode next() jusqu’à ce que toutes les données soient chargées. Comme l’illustre l’exemple précédent, vous pouvez déterminer ce moment en vérifiant la propriété complete de l’objet SQLResult, qui est créée chaque fois que la méthode execute() ou next() prend fin.

Remarque : utilisez le paramètre prefetch et la méthode next() pour diviser le traitement des résultats d’une requête. En revanche, ne vous en servez pas pour obtenir un sous-ensemble de résultats. Si seul un sous-jeu des résultats d’une instruction vous intéresse, utilisez la clause LIMIT de l’instruction SELECT . Si le jeu de résultats est volumineux, rien ne vous empêche de diviser son traitement à l’aide du paramètre prefetch et de la méthode next() .
Envisagez d’utiliser plusieurs objets SQLConnection asynchrones avec une seule base de données pour exécuter simultanément plusieurs instructions.

Lorsqu’un objet SQLConnection est connecté à une base de données à l’aide de la méthode openAsync() , il s’exécute en arrière-plan et non dans le thread d’exécution principal. En outre, chaque objet SQLConnection s’exécute dans son propre thread en arrière-plan. Si vous utilisez plusieurs objets SQLConnection, vous pouvez exécuter simultanément plusieurs instructions SQL de manière efficace.

Cette technique présente néanmoins quelques inconvénients. En particulier, chaque objet SQLConnection supplémentaire requiert de la mémoire. En outre, les exécutions simultanées sollicitent toujours plus le processeur, surtout sur les machines dotées d’une unité centrale ou d’un cœur d’unité centrale unique. C’est pourquoi cette technique n’est pas recommandée sur les périphériques mobiles.

Autre inconvénient, vous risquez de perdre les avantages que présente potentiellement cette technique, car un objet SQLStatement est lié à un objet SQLConnection unique. Il est donc impossible de réutiliser l’objet SQLStatement si l’objet SQLConnection associé est en cours d’utilisation.

Si vous décidez d’utiliser plusieurs objets SQLConnection connectés à une seule base de données, souvenez-vous que chacun d’eux exécute ses instructions dans une transaction qui lui est propre. Vous devrez tenir compte de ce facteur dans tout code qui effectue des modifications (ajout, modification ou suppression de données, par exemple).

Paul Robertson a créé une bibliothèque de code de type open source qui permet de bénéficier des avantages liés à l’utilisation de plusieurs objets SQLConnection, tout en réduisant au minimum les inconvénients potentiels. Cette bibliothèque utilise un pool d’objets SQLConnection et gère les objets SQLStatement associés. Elle garantit que les objets SQLStatement sont ainsi réutilisés et que plusieurs objets SQLConnection sont disponibles pour exécuter simultanément plusieurs instructions. Pour plus d’informations et pour télécharger la bibliothèque, aller à http://probertson.com/projects/air-sqlite/ (disponible en anglais uniquement).