Android bietet integrierte Unterstützung für SQLite, eine effiziente SQL-Datenbank. Beachten Sie diese Best Practices, um die Leistung Ihrer App zu optimieren und dafür zu sorgen, dass sie auch bei steigenden Datenmengen schnell und zuverlässig bleibt. Wenn Sie diese Best Practices beachten, verringern Sie auch die Wahrscheinlichkeit, dass Leistungsprobleme auftreten, die sich nur schwer reproduzieren und beheben lassen.
Beachten Sie die folgenden Leistungsprinzipien, um die Leistung zu steigern:
Weniger Zeilen und Spalten lesen: Optimieren Sie Ihre Abfragen, damit nur die erforderlichen Daten abgerufen werden. Minimieren Sie die Menge der aus der Datenbank gelesenen Daten, da sich ein übermäßiger Datenabruf auf die Leistung auswirken kann.
Arbeit an die SQLite-Engine übergeben: Führen Sie Berechnungen, Filter- und Sortiervorgänge in den SQL-Abfragen aus. Die Verwendung der Abfrage-Engine von SQLite kann die Leistung erheblich verbessern.
Datenbankschema ändern: Entwerfen Sie Ihr Datenbankschema so, dass SQLite effiziente Abfragepläne und Datendarstellungen erstellen kann. Tabellen richtig indexieren und Tabellenstrukturen optimieren, um die Leistung zu verbessern.
Außerdem können Sie mit den verfügbaren Tools zur Fehlerbehebung die Leistung Ihrer SQLite-Datenbank messen, um Bereiche zu identifizieren, die optimiert werden müssen.
Wir empfehlen die Verwendung der Jetpack Room-Bibliothek.
Datenbank für die Leistung konfigurieren
Führen Sie die Schritte in diesem Abschnitt aus, um Ihre Datenbank für eine optimale Leistung in SQLite zu konfigurieren.
Write-Ahead-Logging aktivieren
SQLite implementiert Mutationen, indem sie an ein Protokoll angehängt werden, das gelegentlich in die Datenbank komprimiert wird. Dies wird als Write-Ahead-Logging (WAL) bezeichnet.
Aktivieren Sie WAL, es sei denn, Sie verwenden ATTACH
DATABASE
.
Synchronisierungsmodus lockern
Bei der Verwendung von WAL wird standardmäßig bei jedem Commit eine fsync
ausgegeben, um sicherzustellen, dass die Daten das Laufwerk erreichen. Dies verbessert die Datenausfallsicherheit, verlangsamt aber Ihre Commits.
SQLite bietet eine Option zum Stellen des synchronen Modus. Wenn Sie WAL aktivieren, legen Sie den synchronen Modus auf NORMAL
fest:
Kotlin
db.execSQL("PRAGMA synchronous = NORMAL")
Java
db.execSQL("PRAGMA synchronous = NORMAL");
Bei dieser Einstellung kann ein Commit zurückgegeben werden, bevor die Daten auf einem Laufwerk gespeichert werden. Wenn das Gerät heruntergefahren wird, z. B. bei einem Stromausfall oder einer Kernel-Panik, gehen die verbindlichen Daten möglicherweise verloren. Aufgrund der Protokollierung ist Ihre Datenbank jedoch nicht beschädigt.
Wenn nur Ihre App abstürzt, gelangen Ihre Daten trotzdem auf das Laufwerk. Bei den meisten Apps führt diese Einstellung zu Leistungsverbesserungen ohne nennenswerte Kosten.
Effizientere Tabellenschemas definieren
Definieren Sie ein effizientes Tabellenschema, um die Leistung zu optimieren und den Datenverbrauch zu minimieren. SQLite erstellt effiziente Abfragepläne und Daten, was zu einem schnelleren Abrufen von Daten führt. Dieser Abschnitt enthält Best Practices zum Erstellen von Tabellenschemata.
Achte auf INTEGER PRIMARY KEY
Definieren und füllen Sie für dieses Beispiel eine Tabelle so aus:
CREATE TABLE Customers(
id INTEGER,
name TEXT,
city TEXT
);
INSERT INTO Customers Values(456, 'John Lennon', 'Liverpool, England');
INSERT INTO Customers Values(123, 'Michael Jackson', 'Gary, IN');
INSERT INTO Customers Values(789, 'Dolly Parton', 'Sevier County, TN');
Die Tabellenausgabe sieht so aus:
rowid | id | Name | Stadt |
---|---|---|---|
1 | 456 | John Lennon | Liverpool, England |
2 | 123 | Michael Jackson | Gary, IN |
3 | 789 | Dolly Parton | Sevier County, Tennessee |
Die Spalte rowid
ist ein Index, der den Einfügevorgang beibehält. Abfragen, die nach rowid
filtern, werden als schnelle B-Baumsuche implementiert. Abfragen, die nach id
filtern, sind jedoch ein langsamer Tabellenscan.
Wenn Sie nach id
suchen möchten, können Sie das Speichern der Spalte rowid
vermeiden. So wird weniger Speicherplatz belegt und die Datenbank insgesamt schneller:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
city TEXT
);
Ihre Tabelle sieht jetzt so aus:
id | Name | Stadt |
---|---|---|
123 | Michael Jackson | Gary, IN |
456 | John Lennon | Liverpool, England |
789 | Dolly Parton | Sevier County, Tennessee |
Da die Spalte rowid
nicht gespeichert werden muss, sind id
-Abfragen schnell. Die Tabelle wird jetzt nach id
statt nach Anzeigenauftrag sortiert.
Abfragen mit Indexen beschleunigen
SQLite verwendet Indexe, um Abfragen zu beschleunigen. Wenn Sie eine Spalte filtern (WHERE
), sortieren (ORDER BY
) oder aggregieren (GROUP BY
) und die Tabelle einen Index für die Spalte hat, wird die Abfrage beschleunigt.
Im vorherigen Beispiel muss die gesamte Tabelle gescannt werden, um nach city
zu filtern:
SELECT id, name
WHERE city = 'London, England';
Bei einer App mit vielen Abfragen nach Städten können Sie diese Abfragen mit einem Index beschleunigen:
CREATE INDEX city_index ON Customers(city);
Ein Index wird als zusätzliche Tabelle implementiert, nach der Indexspalte sortiert und rowid
zugeordnet:
Stadt | rowid |
---|---|
Gary, IN | 2 |
Liverpool, England | 1 |
Sevier County, Tennessee | 3 |
Die Speicherkosten für die Spalte city
sind jetzt doppelt so hoch, da sie jetzt sowohl in der ursprünglichen Tabelle als auch im Index vorhanden ist. Da Sie den Index verwenden, sind die zusätzlichen Speicherkosten die schnelleren Abfragen wert.
Behalten Sie jedoch keinen Index bei, den Sie nicht verwenden, um Speicherkosten zu vermeiden, ohne dass sich die Abfrageleistung verbessert.
Mehrspaltige Indexe erstellen
Wenn Sie in Ihren Abfragen mehrere Spalten kombinieren, können Sie Indexe mit mehreren Spalten erstellen, um die Abfrage zu beschleunigen. Sie können auch einen Index für eine äußere Spalte verwenden und die interne Suche als linearen Scan ausführen lassen.
Beispiel:
SELECT id, name
WHERE city = 'London, England'
ORDER BY city, name
Sie können die Abfrage mit einem Index mit mehreren Spalten in der Reihenfolge beschleunigen, die in der Abfrage angegeben ist:
CREATE INDEX city_name_index ON Customers(city, name);
Wenn Sie jedoch nur einen Index für city
haben, wird die äußere Sortierung trotzdem beschleunigt, während für die innere Sortierung ein linearer Scan erforderlich ist.
Das funktioniert auch bei Suchanfragen mit Präfix. Ein Index ON Customers (city, name)
beschleunigt beispielsweise auch das Filtern, Sortieren und Gruppieren nach city
, da die Indextabelle für einen mehrspaltigen Index nach den angegebenen Indexen in der angegebenen Reihenfolge sortiert wird.
Achte auf WITHOUT ROWID
Standardmäßig erstellt SQLite eine rowid
-Spalte für Ihre Tabelle, wobei rowid
ein impliziter INTEGER PRIMARY KEY AUTOINCREMENT
ist. Wenn Sie bereits eine Spalte mit dem Namen INTEGER PRIMARY KEY
haben, wird diese Spalte zu einem Alias von rowid
.
Für Tabellen mit einem anderen Primärschlüssel als INTEGER
oder einem zusammengesetzten Schlüssel aus mehreren Spalten können Sie WITHOUT
ROWID
verwenden.
Kleine Daten als BLOB
und große Daten als Datei speichern
Wenn Sie einer Zeile große Daten zuordnen möchten, z. B. ein Thumbnail eines Bilds oder ein Foto für einen Kontakt, können Sie die Daten entweder in einer BLOB
-Spalte oder in einer Datei speichern und dann den Dateipfad in der Spalte speichern.
Dateien werden in der Regel auf 4 KB aufgerundet. Bei sehr kleinen Dateien, bei denen der Rundungsfehler erheblich ist, ist es effizienter, sie in der Datenbank als BLOB
zu speichern. SQLite minimiert Dateisystemaufrufe und ist in einigen Fällen schneller als das zugrunde liegende Dateisystem.
Abfrageleistung verbessern
Mit diesen Best Practices können Sie die Abfrageleistung in SQLite verbessern, indem Sie die Antwortzeiten minimieren und die Verarbeitungseffizienz maximieren.
Nur die benötigten Zeilen lesen
Mit Filtern können Sie die Ergebnisse eingrenzen, indem Sie bestimmte Kriterien wie Zeitraum, Standort oder Name angeben. Mithilfe von Limits können Sie die Anzahl der angezeigten Ergebnisse steuern:
Kotlin
db.rawQuery(""" SELECT name FROM Customers LIMIT 10; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT name FROM Customers LIMIT 10; """, null)) { while (cursor.moveToNext()) { ... } }
Nur die benötigten Spalten lesen
Wählen Sie keine nicht benötigten Spalten aus, da dies Ihre Abfragen verlangsamen und Ressourcen verschwenden kann. Wählen Sie stattdessen nur Spalten aus, die verwendet werden.
Im folgenden Beispiel wählen Sie id
, name
und phone
aus:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery( """ SELECT id, name, phone FROM customers; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { val name = cursor.getString(1) // ... } }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT id, name, phone FROM customers; """, null)) { while (cursor.moveToNext()) { String name = cursor.getString(1); ... } }
Sie benötigen jedoch nur die Spalte name
:
Kotlin
db.rawQuery(""" SELECT name FROM Customers; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { val name = cursor.getString(0) ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT name FROM Customers; """, null)) { while (cursor.moveToNext()) { String name = cursor.getString(0); ... } }
Abfragen mit SQL-Karten und nicht mit Stringkonkatenierung parametrisieren
Ihr Abfragestring kann einen Parameter enthalten, der erst zur Laufzeit bekannt ist, z. B.:
Kotlin
fun getNameById(id: Long): String? db.rawQuery( "SELECT name FROM customers WHERE id=$id", null ).use { cursor -> return if (cursor.moveToFirst()) { cursor.getString(0) } else { null } } }
Java
@Nullable public String getNameById(long id) { try (Cursor cursor = db.rawQuery( "SELECT name FROM customers WHERE id=" + id, null)) { if (cursor.moveToFirst()) { return cursor.getString(0); } else { return null; } } }
Im obigen Code wird für jede Abfrage ein anderer String erstellt, sodass der Anweisungscache nicht genutzt werden kann. Jeder Aufruf muss von SQLite kompiliert werden, bevor er ausgeführt werden kann. Stattdessen können Sie das id
-Argument durch einen Parameter ersetzen und den Wert mit selectionArgs
verknüpfen:
Kotlin
fun getNameById(id: Long): String? { db.rawQuery( """ SELECT name FROM customers WHERE id=? """.trimIndent(), arrayOf(id.toString()) ).use { cursor -> return if (cursor.moveToFirst()) { cursor.getString(0) } else { null } } }
Java
@Nullable public String getNameById(long id) { try (Cursor cursor = db.rawQuery(""" SELECT name FROM customers WHERE id=? """, new String[] {String.valueOf(id)})) { if (cursor.moveToFirst()) { return cursor.getString(0); } else { return null; } } }
Jetzt kann die Abfrage einmal kompiliert und im Cache gespeichert werden. Die kompilierte Abfrage wird bei verschiedenen Aufrufen von getNameById(long)
wiederverwendet.
Iterationen in SQL, nicht im Code
Verwenden Sie eine einzelne Abfrage, die alle gewünschten Ergebnisse zurückgibt, anstelle einer programmatischen Schleife, die SQL-Abfragen iteriert, um einzelne Ergebnisse zurückzugeben. Die programmatische Schleife ist etwa 1.000-mal langsamer als eine einzelne SQL-Abfrage.
DISTINCT
für eindeutige Werte verwenden
Mit dem Keyword DISTINCT
lässt sich die Leistung Ihrer Abfragen verbessern, da die zu verarbeitende Datenmenge reduziert wird. Wenn Sie beispielsweise nur die eindeutigen Werte aus einer Spalte zurückgeben möchten, verwenden Sie DISTINCT
:
Kotlin
db.rawQuery(""" SELECT DISTINCT name FROM Customers; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { // Only iterate over distinct names in Kotlin ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT DISTINCT name FROM Customers; """, null)) { while (cursor.moveToNext()) { // Only iterate over distinct names in Java ... } }
Verwenden Sie nach Möglichkeit Aggregatfunktionen.
Verwenden Sie Aggregatfunktionen für aggregierte Ergebnisse ohne Zeilendaten. Im folgenden Code wird beispielsweise geprüft, ob mindestens eine übereinstimmende Zeile vorhanden ist:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery(""" SELECT id, name FROM Customers WHERE city = 'Paris'; """.trimIndent(), null ).use { cursor -> if (cursor.moveToFirst()) { // At least one customer from Paris ... } else { // No customers from Paris ... }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT id, name FROM Customers WHERE city = 'Paris'; """, null)) { if (cursor.moveToFirst()) { // At least one customer from Paris ... } else { // No customers from Paris ... } }
Wenn Sie nur die erste Zeile abrufen möchten, können Sie EXISTS()
verwenden, um 0
zurückzugeben, wenn keine übereinstimmende Zeile vorhanden ist, und 1
, wenn eine oder mehrere Zeilen übereinstimmen:
Kotlin
db.rawQuery(""" SELECT EXISTS ( SELECT null FROM Customers WHERE city = 'Paris'; ); """.trimIndent(), null ).use { cursor -> if (cursor.moveToFirst() && cursor.getInt(0) == 1) { // At least one customer from Paris ... } else { // No customers from Paris ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT EXISTS ( SELECT null FROM Customers WHERE city = 'Paris' ); """, null)) { if (cursor.moveToFirst() && cursor.getInt(0) == 1) { // At least one customer from Paris ... } else { // No customers from Paris ... } }
Verwenden Sie SQLite-Aggregatfunktionen in Ihrem App-Code:
COUNT
: Zählt die Anzahl der Zeilen in einer Spalte.SUM
: Hiermit werden alle numerischen Werte in einer Spalte addiert.MIN
oderMAX
: Bestimmt den niedrigsten oder höchsten Wert. Funktioniert für numerische Spalten,DATE
-Typen und Texttypen.AVG
: Der Mittelwert wird berechnet.GROUP_CONCAT
: Strings werden mit einem optionalen Trennzeichen verknüpft.
COUNT()
anstelle von Cursor.getCount()
verwenden
Im folgenden Beispiel liest die Funktion Cursor.getCount()
alle Zeilen aus der Datenbank und gibt alle Zeilenwerte zurück:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery(""" SELECT id FROM Customers; """.trimIndent(), null ).use { cursor -> val count = cursor.getCount() }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT id FROM Customers; """, null)) { int count = cursor.getCount(); ... }
Wenn Sie jedoch COUNT()
verwenden, gibt die Datenbank nur die Anzahl zurück:
Kotlin
db.rawQuery(""" SELECT COUNT(*) FROM Customers; """.trimIndent(), null ).use { cursor -> cursor.moveToFirst() val count = cursor.getInt(0) }
Java
try (Cursor cursor = db.rawQuery(""" SELECT COUNT(*) FROM Customers; """, null)) { cursor.moveToFirst(); int count = cursor.getInt(0); ... }
Nest-Abfragen anstelle von Code
SQL ist kombinierbar und unterstützt Unterabfragen, Joins und Fremdschlüsseleinschränkungen. Sie können das Ergebnis einer Abfrage in einer anderen Abfrage verwenden, ohne den App-Code durchgehen zu müssen. Dadurch müssen weniger Daten aus SQLite kopiert werden und die Datenbank-Engine kann Ihre Abfrage optimieren.
Im folgenden Beispiel können Sie eine Abfrage ausführen, um herauszufinden, in welcher Stadt die meisten Kunden sind, und dann das Ergebnis in einer anderen Abfrage verwenden, um alle Kunden aus dieser Stadt zu finden:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery(""" SELECT city FROM Customers GROUP BY city ORDER BY COUNT(*) DESC LIMIT 1; """.trimIndent(), null ).use { cursor -> if (cursor.moveToFirst()) { val topCity = cursor.getString(0) db.rawQuery(""" SELECT name, city FROM Customers WHERE city = ?; """.trimIndent(), arrayOf(topCity)).use { innerCursor -> while (innerCursor.moveToNext()) { ... } } } }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT city FROM Customers GROUP BY city ORDER BY COUNT(*) DESC LIMIT 1; """, null)) { if (cursor.moveToFirst()) { String topCity = cursor.getString(0); try (Cursor innerCursor = db.rawQuery(""" SELECT name, city FROM Customers WHERE city = ?; """, new String[] {topCity})) { while (innerCursor.moveToNext()) { ... } } } }
Wenn Sie das Ergebnis in der Hälfte der Zeit des vorherigen Beispiels erhalten möchten, verwenden Sie eine einzelne SQL-Abfrage mit verschachtelten Anweisungen:
Kotlin
db.rawQuery(""" SELECT name, city FROM Customers WHERE city IN ( SELECT city FROM Customers GROUP BY city ORDER BY COUNT (*) DESC LIMIT 1; ); """.trimIndent(), null ).use { cursor -> if (cursor.moveToNext()) { ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT name, city FROM Customers WHERE city IN ( SELECT city FROM Customers GROUP BY city ORDER BY COUNT(*) DESC LIMIT 1 ); """, null)) { while(cursor.moveToNext()) { ... } }
Eindeutigkeit in SQL prüfen
Wenn eine Zeile nur dann eingefügt werden darf, wenn ein bestimmter Spaltenwert in der Tabelle eindeutig ist, ist es möglicherweise effizienter, diese Eindeutigkeit als Spalteneinschränkung zu erzwingen.
Im folgenden Beispiel wird eine Abfrage ausgeführt, um die einzufügende Zeile zu validieren, und eine weitere, um sie tatsächlich einzufügen:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery( """ SELECT EXISTS ( SELECT null FROM customers WHERE username = ? ); """.trimIndent(), arrayOf(customer.username) ).use { cursor -> if (cursor.moveToFirst() && cursor.getInt(0) == 1) { throw AddCustomerException(customer) } } db.execSQL( "INSERT INTO customers VALUES (?, ?, ?)", arrayOf( customer.id.toString(), customer.name, customer.username ) )
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT EXISTS ( SELECT null FROM customers WHERE username = ? ); """, new String[] { customer.username })) { if (cursor.moveToFirst() && cursor.getInt(0) == 1) { throw new AddCustomerException(customer); } } db.execSQL( "INSERT INTO customers VALUES (?, ?, ?)", new String[] { String.valueOf(customer.id), customer.name, customer.username, });
Anstatt die eindeutige Einschränkung in Kotlin oder Java zu prüfen, können Sie sie in SQL prüfen, wenn Sie die Tabelle definieren:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
username TEXT UNIQUE
);
SQLite führt Folgendes aus:
CREATE TABLE Customers(...);
CREATE UNIQUE INDEX CustomersUsername ON Customers(username);
Jetzt können Sie eine Zeile einfügen und SQLite die Einschränkung prüfen lassen:
Kotlin
try { db.execSql( "INSERT INTO Customers VALUES (?, ?, ?)", arrayOf(customer.id.toString(), customer.name, customer.username) ) } catch(e: SQLiteConstraintException) { throw AddCustomerException(customer, e) }
Java
try { db.execSQL( "INSERT INTO Customers VALUES (?, ?, ?)", new String[] { String.valueOf(customer.id), customer.name, customer.username, }); } catch (SQLiteConstraintException e) { throw new AddCustomerException(customer, e); }
SQLite unterstützt eindeutige Indexe mit mehreren Spalten:
CREATE TABLE table(...);
CREATE UNIQUE INDEX unique_table ON table(column1, column2, ...);
SQLite validiert Einschränkungen schneller und mit weniger Overhead als Kotlin- oder Java-Code. Es empfiehlt sich, SQLite anstelle von App-Code zu verwenden.
Mehrere Einträge in einer einzigen Transaktion einfügen
Mit einer Transaktion werden mehrere Vorgänge verbindlich ausgeführt, was nicht nur die Effizienz, sondern auch die Korrektheit verbessert. Um die Datenkonsistenz zu verbessern und die Leistung zu beschleunigen, können Sie mehrere Einträge gleichzeitig einfügen:
Kotlin
db.beginTransaction() try { customers.forEach { customer -> db.execSql( "INSERT INTO Customers VALUES (?, ?, ...)", arrayOf(customer.id.toString(), customer.name, ...) ) } } finally { db.endTransaction() }
Java
db.beginTransaction(); try { for (customer : Customers) { db.execSQL( "INSERT INTO Customers VALUES (?, ?, ...)", new String[] { String.valueOf(customer.id), customer.name, ... }); } } finally { db.endTransaction() }
Tools zur Fehlerbehebung verwenden
SQLite bietet die folgenden Tools zur Fehlerbehebung, mit denen Sie die Leistung messen können.
Interaktiven Prompt von SQLite verwenden
Führen Sie SQLite auf Ihrem Computer aus, um Abfragen auszuführen und mehr zu erfahren.
Für verschiedene Android-Plattformversionen werden unterschiedliche SQLite-Versionen verwendet. Wenn Sie dieselbe Engine verwenden möchten, die auf einem Android-Gerät verwendet wird, verwenden Sie adb shell
und führen Sie sqlite3
auf Ihrem Zielgerät aus.
Sie können SQLite bitten, Abfragen zu messen:
sqlite> .timer on
sqlite> SELECT ...
Run Time: real ... user ... sys ...
EXPLAIN QUERY PLAN
Sie können SQLite bitten, zu erklären, wie eine Abfrage beantwortet werden soll. Verwenden Sie dazu EXPLAIN QUERY PLAN
:
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SCAN Customers
Im vorherigen Beispiel ist ein vollständiger Tabellenscan ohne Index erforderlich, um alle Kunden aus Paris zu finden. Dies wird als lineare Komplexität bezeichnet. SQLite muss alle Zeilen lesen und nur die behalten, die mit Kunden aus Paris übereinstimmen. Sie können das Problem beheben, indem Sie einen Index hinzufügen:
sqlite> CREATE INDEX Idx1 ON Customers(city);
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SEARCH test USING INDEX Idx1 (city=?
Wenn Sie die interaktive Shell verwenden, können Sie SQLite anweisen, Abfragepläne immer zu erläutern:
sqlite> .eqp on
Weitere Informationen finden Sie unter Abfrageplanung.
SQLite-Analysetool
SQLite bietet die Befehlszeile sqlite3_analyzer
, mit der zusätzliche Informationen gedumpt werden können, die zur Behebung von Leistungsproblemen verwendet werden können. Informationen zur Installation finden Sie auf der SQLite-Downloadseite.
Mit adb pull
können Sie eine Datenbankdatei von einem Zielgerät zur Analyse auf Ihre Workstation herunterladen:
adb pull /data/data/<app_package_name>/databases/<db_name>.db
SQLite-Browser
Sie können auch das GUI-Tool SQLite Browser auf der Downloadseite von SQLite installieren.
Android-Protokollierung
Android protokolliert die Ausführungszeiten von SQLite-Abfragen:
# Enable query time logging
$ adb shell setprop log.tag.SQLiteTime VERBOSE
# Disable query time logging
$ adb shell setprop log.tag.SQLiteTime ERROR
Perfetto-Tracing
Wenn Sie Perfetto konfigurieren, können Sie Folgendes hinzufügen, um Tracks für einzelne Abfragen einzubeziehen:
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
atrace_categories: "database"
}
}
}
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Benchmarks in der Continuous Integration ausführen
- Eingefrorene Frames
- Grundlagenprofile ohne Macrobenchmark erstellen und messen