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 changentjournal_mode
etsynchronous
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 lentINSERT/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 !