SQLite
 sql >> Base de données >  >> RDS >> SQLite

Optimisation des performances SQLite

SQLite est une base de données relationnelle populaire que vous intégrez dans votre application. Avec une quantité croissante de données dans votre base de données, vous devez appliquer le réglage des performances SQLite. Cet article traite des indices et de leurs pièges, de l'utilisation du planificateur de requêtes, du mode journal Write-Ahead-Logging (WAL) et de l'augmentation de la taille du cache. Il explique également l'importance de mesurer l'impact de vos ajustements à l'aide de tests automatisés.

Présentation

SQLite est un système de base de données relationnelle (DB) populaire . Contrairement à ses grands frères basés sur client-serveur, tels que MySQL, SQLite peut être intégré dans votre application en tant que bibliothèque . SQLite a un ensemble de fonctionnalités très similaire et peut également gérer des millions de lignes, étant donné que vous connaissez quelques trucs et astuces sur le réglage des performances. Comme les sections suivantes le montreront, il y a plus à savoir sur le réglage des performances SQLite que la simple création d'index.

Créer des index, mais avec prudence

L'idée de base d'un index est d'accélérer la lecture de données spécifiques , c'est-à-dire SELECT instructions avec un WHERE clause. Les indices accélèrent également le tri données (ORDER BY ), ou JOIN tableaux. Malheureusement, les index sont une épée à double tranchant, car ils consomment de l'espace disque supplémentaire et ils ralentissent la manipulation des données (INSERT , UPDATE , DELETE ).

Le conseil général est de créer le moins d'index possible, mais autant que nécessaire . De plus, les indices n'ont de sens que pour plus grand bases de données, avec des milliers ou des millions de lignes.

Utilisez le planificateur de requêtes pour analyser vos requêtes

La façon dont les index sont utilisés en interne par SQLite est documentée, mais pas très facile à comprendre. Comme expliqué plus en détail dans cet article, c'est une bonne idée d'analyser une requête en la préfixant avec EXPLAIN QUERY PLAN . Examinez chaque ligne de sortie, dont il existe trois variantes de base :

  • SEARCH table ... les lignes sont un bon signe - SQLite utilise l'un de vos index !
  • SCAN table ... USING INDEX est un mauvais signe,
  • SCAN table ... c'est encore pire !

Essayez d'éviter la table SCAN table [using index] entrées dans la sortie de EXPLAIN QUERY PLAN chaque fois que possible, car vous rencontrerez des problèmes de performances sur des bases de données plus volumineuses. Utilisez EXPLAIN QUERY PLAN pour ajouter ou modifier itérativement vos index jusqu'à ce qu'il n'y ait plus de SCAN table les entrées apparaissent.

Optimiser les requêtes qui impliquent IS NOT

Vérifier IS NOT ... est cher car SQLite devra scanner toutes les lignes de la table, même si la colonne concernée a un index . Les indices ne sont utiles que si vous recherchez des valeurs spécifiques, c'est-à-dire des comparaisons impliquant < (plus petit), > (plus grand), ou = (égal), mais ils ne s'appliquent pas à !=(inégal).

Une petite astuce consiste à remplacer WHERE column != value avec WHERE column > value OR column < value . Cela utilisera l'index de la colonne et affectera efficacement toutes les lignes dont la valeur n'est pas égale à value . De même, un WHERE stringColumn != '' peut être remplacé par WHERE stringColumn > '' , car les chaînes sont triables. Lorsque vous appliquez cette astuce, assurez-vous de savoir comment SQLite gère NULL comparaisons. Par exemple, SQLite évalue NULL > '' comme FALSE .

Si vous utilisez une telle astuce de comparaison, il y a une autre mise en garde au cas où votre requête contiendrait WHERE et ORDER BY , chacun avec une colonne différente :cela rendra à nouveau la requête inefficace. Si possible, utilisez le même colonne dans WHERE et ORDER BY , ou créez un index de couverture qui implique à la fois WHERE et ORDER BY colonne.

Améliorer la vitesse d'écriture avec Write-Ahead-Log

La Write-Ahead-Logging (WAL) le mode journal améliore significativement les performances d'écriture/mise à jour , par rapport au rollback par défaut mode journal. Cependant, comme documenté ici, il y a quelques mises en garde . Par exemple, le mode WAL n'est pas disponible sur certains systèmes d'exploitation. En outre, il existe des garanties de cohérence des données réduites en cas de panne matérielle . Assurez-vous de lire les premières pages pour comprendre ce que vous faites.

J'ai trouvé que la commande PRAGMA synchronous = NORMAL fournit une accélération 3-4x. Définition de journal_mode vers WAL puis améliore à nouveau les performances de manière significative (environ 10 fois ou plus, selon le système d'exploitation).

Outre les mises en garde que j'ai déjà mentionnées, vous devez également être conscient de ce qui suit :

  • En utilisant le mode journal WAL, il y aura deux fichiers supplémentaires à côté du fichier de base de données sur votre système de fichiers, qui ont le même nom que la base de données, mais suffixés "-shm" et "-wal". Normalement, vous n'avez pas besoin de vous en soucier, mais si vous deviez envoyer la base de données à une autre machine pendant que votre application est en cours d'exécution, n'oubliez pas d'inclure ces deux fichiers. SQLite compactera ces deux fichiers dans le fichier principal chaque fois que vous fermerez habituellement toutes les connexions de base de données ouvertes.
  • Les performances d'insertion ou de mise à jour chutent occasionnellement, chaque fois que la requête déclenche la fusion du contenu du fichier journal WAL dans le fichier de base de données principal. C'est ce qu'on appelle le point de contrôle , voir ici.
  • J'ai trouvé que PRAGMA s qui changent journal_mode et synchronous ne semblent pas être stockés de manière persistante dans la base de données. Ainsi, je toujours réexécutez-les chaque fois que j'ouvre une nouvelle connexion à la base de données, plutôt que de simplement les exécuter lors de la création des tables pour la première fois.

Mesurer tout

Chaque fois que vous ajoutez des ajustements de performances, assurez-vous d'en mesurer l'impact. Les tests (unitaires) automatisés sont une excellente approche pour cela. Ils aident à documenter vos conclusions pour votre équipe, et ils découvriront les comportements déviants au fil du temps , par exemple. lorsque vous mettez à jour vers une version plus récente de SQLite. Exemples de ce que vous pouvez mesurer :

  • Quel est l'effet de l'utilisation de WAL mode journal sur le rollback mode? Quel est l'effet d'autres PRAGMA améliorant (soi-disant) les performances ? s ?
  • Une fois que vous avez ajouté/modifié/supprimé un index, à quel point SELECT est-il plus rapide ? déclarations deviennent? Combien plus lent INSERT/DELETE/UPDATE déclarations deviennent ?
  • Combien d'espace disque supplémentaire les index consomment-ils ?

Pour chacun de ces tests, envisagez de les répéter avec différentes tailles de base de données. Par exemple. exécutez-les sur une base de données vide, ainsi que sur une base de données qui contient déjà des milliers (ou des millions) d'entrées. Vous devez également exécuter les tests sur différents appareils et systèmes d'exploitation, en particulier si votre environnement de développement et de production est sensiblement différent.

Ajuster la taille du cache

SQLite stocke les informations temporaires dans un cache (dans la RAM), par ex. lors de la construction des résultats d'un SELECT requête, ou lors de la manipulation de données qui n'ont pas encore été validées. Par défaut, cette taille n'est que de 2 Mo . Les machines de bureau modernes peuvent épargner beaucoup plus. Exécutez PRAGMA cache_size = -kibibytes pour augmenter cette valeur (attention au moins signe devant la valeur !). Voir ici pour plus d'informations. Encore une fois, mesurez quel impact ce paramètre a-t-il sur les performances !

Utilisez REPLACE INTO pour créer ou mettre à jour une ligne

Il ne s'agit peut-être pas tant d'un ajustement des performances que d'une petite astuce. Supposons que vous deviez mettre à jour une ligne dans la table t , ou créer une ligne si elle n'existe pas encore. Plutôt que d'utiliser deux requêtes (SELECT suivi de INSERT ou UPDATE ), utilisez le REPLACE INTO (documents officiels).

Pour que cela fonctionne, ajoutez une colonne factice supplémentaire (par exemple, replacer ) à la table t , qui a un UNIQUE contraindre. La déclaration de la colonne pourrait par ex. be ... replacer INTEGER UNIQUE ... qui fait partie de votre CREATE TABLE déclaration. Utilisez ensuite une requête telle que

REPLACE INTO t (col1, col2, ..., replacer) VALUES (?,?,...,1)Code language: SQL (Structured Query Language) (sql)

Lorsque cette requête s'exécute pour la première fois, elle effectuera simplement un INSERT . Lorsqu'il est exécuté la deuxième fois, le UNIQUE contrainte du replacer colonne se déclenchera et le comportement de résolution de conflit entraînera la suppression de l'ancienne ligne, en créant une nouvelle automatiquement. Vous pouvez également trouver la commande UPSERT associée utile.

Conclusion

Une fois que le nombre de lignes dans votre base de données augmente, les ajustements de performances deviennent une nécessité. Les indices sont la solution la plus courante. Ils échangent une complexité temporelle améliorée contre une complexité spatiale réduite, améliorant les vitesses de lecture, tout en affectant négativement les performances de modification des données. A J'ai démontré, vous devez être très prudent lorsque vous comparez pour l'inégalité dans SELECT instructions, car SQLite ne peut pas utiliser d'index pour ce type de comparaisons. Je recommande généralement d'utiliser le planificateur de requêtes qui explique ce qui se passe en interne pour chaque requête SQL. Chaque fois que vous modifiez quelque chose, mesurez l'impact !