Best Practices für die SQLite-Leistung

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. Mit diesen Best Practices verringern Sie auch die Wahrscheinlichkeit, Es stößt auf Leistungsprobleme, die sich nur schwer reproduzieren lassen, zur Fehlerbehebung.

Beachten Sie die folgenden Leistungsprinzipien, um eine höhere Leistung zu erzielen:

  • Weniger Zeilen und Spalten anzeigen: Optimieren Sie Ihre Abfragen, um nur die notwendige Daten. Minimieren Sie die aus der Datenbank gelesene Datenmenge, kann sich ein übermäßiger Datenabruf negativ auf die Leistung auswirken.

  • Arbeit an die SQLite-Engine übergeben: Berechnungen, Filter- und Sortiervorgänge in den SQL-Abfragen ausführen. Die Verwendung der Abfrage-Engine von SQLite kann erheblich die Leistung zu verbessern.

  • Datenbankschema ändern: Datenbankschema zur Unterstützung von SQLite entwerfen effiziente Abfragepläne und Datendarstellungen zu erstellen. Tabellen richtig indexieren und optimieren Sie Tabellenstrukturen, um die Leistung zu verbessern.

Außerdem können Sie mit den verfügbaren Tools zur Fehlerbehebung SQLite-Datenbank, um Bereiche zu identifizieren, Optimierung.

Wir empfehlen die Verwendung der Jetpack Room-Bibliothek.

Datenbank für Leistung konfigurieren

Führen Sie die Schritte in diesem Abschnitt aus, um Ihre Datenbank für eine optimale die Performance in SQLite.

Write-Ahead-Logging aktivieren

SQLite implementiert Mutationen durch Anhängen an ein Log, was gelegentlich in die Datenbank komprimiert. Dies wird als Write-Ahead-Logging (WAL).

Aktivieren WAL außer 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 Langlebigkeit der Daten, verlangsamt aber Commits übergeben.

SQLite bietet eine Option, um synchrone Modus an. 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 sind. Wenn ein wenn das Gerät heruntergefahren wird, beispielsweise bei Stromausfall oder bei einer Kernel-Panic, übergebene Daten verloren gehen. Aufgrund des Loggings wird Ihre Datenbank korrumpiert.

Wenn nur Ihre App abstürzt, gelangen Ihre Daten trotzdem auf das Laufwerk. Bei den meisten Apps ohne materielle Kosten Leistungsverbesserungen zu erzielen.

Effiziente 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 schnelleren Datenabruf. Dieser Abschnitt enthält Best Practices zum Erstellen einer Tabelle Schemas.

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 (Zeilen-ID) 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 Index, der den Anzeigenauftrag beibehält. Suchanfragen, die nach rowid filtern, werden als schnelle B-Baum-Suche implementiert. Suchanfragen, die Nach id filtern ist ein langsamer Tabellenscan.

Wenn Sie Suchvorgänge bis zum id durchführen möchten, können Sie das Speichern des rowid-Spalte für weniger Daten im Speicher und eine schnellere Datenbank:

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 Sie die Spalte rowid nicht speichern müssen, sind id-Abfragen schnell. Hinweis , dass die Tabelle jetzt basierend auf id und nicht nach dem Anzeigenauftrag sortiert ist.

Abfragen mithilfe von Indexen beschleunigen

SQLite nutzt Indexe um Abfragen zu beschleunigen. Beim Filtern (WHERE), Sortieren (ORDER BY) oder Beim Aggregieren (GROUP BY) einer Spalte, wenn die Tabelle einen Index für die Spalte hat, Abfrage beschleunigt wird.

Im vorherigen Beispiel muss zum Filtern nach city die gesamte Tabelle gescannt werden:

SELECT id, name
WHERE city = 'London, England';

Bei einer Anwendung mit vielen Suchanfragen nach Städten können Sie diese Abfragen mit einem Index:

CREATE INDEX city_index ON Customers(city);

Ein Index wird als zusätzliche Tabelle implementiert, sortiert nach der Indexspalte zugeordnet zu rowid:

Stadt Rowid (Zeilen-ID)
Gary, IN 2
Liverpool, England 1
Sevier County, Tennessee 3

Die Speicherkosten der Spalte city sind jetzt doppelt so hoch, die jetzt sowohl in der Originaltabelle als auch im Index vorhanden sind. Da Sie die Methode Index ist, sind die Kosten für zusätzlichen Speicher den Nutzen schnellerer Abfragen wert. Pflegen Sie jedoch keinen Index, den Sie nicht nutzen, wenn keine Steigerung der Abfrageleistung erforderlich ist.

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 einen Index auch für eine externe Spalte verwenden und lassen Sie die Suche innerhalb als linearen Scan durchführen.

Nehmen wir zum Beispiel die folgende Abfrage:

SELECT id, name
WHERE city = 'London, England'
ORDER BY city, name

Sie können die Abfrage mit einem mehrspaltigen Index in der gleichen Reihenfolge wie in der Abfrage angegeben:

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. Beispiel: Ein Index ON Customers (city, name) beschleunigt auch das Filtern, Sortieren und Gruppieren. nach city, da die Indextabelle für einen mehrspaltigen Index nach dem in der angegebenen Reihenfolge angezeigt werden sollen.

WITHOUT ROWID in Betracht ziehen

Standardmäßig erstellt SQLite eine rowid-Spalte für Ihre Tabelle, wobei rowid ein implizites INTEGER PRIMARY KEY AUTOINCREMENT 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 Primärschlüssel 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 Bildes 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 in Schritten von 4 KB aufgerundet. Bei sehr kleinen Dateien, der Rundungsfehler erheblich ist, ist es effizienter, sie in der Datenbank als BLOB. SQLite minimiert Dateisystemaufrufe und ist schneller als die zugrunde liegendes Dateisystem .

Abfrageleistung verbessern

Befolgen Sie diese Best Practices, um die Abfrageleistung in SQLite zu verbessern, indem Sie Reaktionszeiten und maximale Verarbeitungseffizienz.

Nur die benötigten Zeilen lesen

Mit Filtern können Sie die Ergebnisse eingrenzen, indem Sie bestimmte Kriterien, wie Zeitraum, Standort oder Name. Mit Limits können Sie die Anzahl der der angezeigten Ergebnisse:

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

Vermeiden Sie die Auswahl nicht benötigter Spalten, Abfragen verlangsamen und Ressourcen verschwenden. 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 vorhergehenden Code konstruiert jede Abfrage einen anderen String. hat keinen Nutzen von dem Anweisungs-Cache. Für jeden Aufruf muss SQLite kompilieren bevor sie ausgeführt werden kann. Stattdessen können Sie das Argument id durch ein parameter und Binden Sie den Wert mit selectionArgs:

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 wiederverwendet zwischen verschiedenen Aufrufen von getNameById(long).

In SQL iterieren, nicht im Code

Verwenden Sie statt einer programmatischen Abfrage eine einzelne Abfrage, die alle Zielergebnisse zurückgibt SQL-Abfragen iterieren, um einzelne Ergebnisse zurückzugeben. Das Programmatik 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 Menge der zu verarbeitenden Daten reduziert wird. Wenn Sie zum Beispiel Um nur die eindeutigen Werte aus einer Spalte zurückzugeben, 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
    ...
  }
}

Nach Möglichkeit Aggregatfunktionen verwenden

Verwenden Sie Aggregatfunktionen für aggregierte Ergebnisse ohne Zeilendaten. Beispiel: Der Parameter Der folgende Code prü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
    ...
  }
}

Um nur die erste Zeile abzurufen, können Sie EXISTS() verwenden, um 0 zurückzugeben, wenn eine Übereinstimmung Zeile nicht vorhanden 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
    ...
  }
}

SQLite-Aggregat verwenden Funktionen in Ihrer App Code:

  • COUNT: zählt, wie viele Zeilen sich in einer Spalte befinden.
  • SUM: Addiert alle numerischen Werte in einer Spalte.
  • MIN oder MAX: Hiermit wird der niedrigste oder höchste Wert festgelegt. Funktioniert für numerische Spalten DATE- und Texttypen.
  • AVG: Ermittelt den numerischen durchschnittlichen Wert.
  • GROUP_CONCAT: verkettet Strings mit einem optionalen Trennzeichen.

COUNT() statt Cursor.getCount() verwenden

Im im folgenden Beispiel Cursor.getCount()-Funktion liest 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 zusammensetzbar und unterstützt Unterabfragen, Joins und Fremdschlüsseleinschränkungen. Sie können das Ergebnis einer Abfrage in einer anderen Abfrage verwenden, ohne die App Code. 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 zu ermitteln, in welcher Stadt Kundschaft finden, verwenden Sie dann das Ergebnis einer anderen Abfrage, um alle Kunden diese Stadt:

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()) {
          ...
        }
    }
  }
}

Um das Ergebnis in der Hälfte der Zeit des vorherigen Beispiels zu erhalten, verwenden Sie ein einzelnes SQL 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 eingefügt werden darf, wenn ein bestimmter Spaltenwert im ist es möglicherweise effizienter, diese Eindeutigkeit als Spalte Einschränkung.

Im folgenden Beispiel wird eine Abfrage ausgeführt, um zu validieren, dass die Zeile und eine weitere zum Einfü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 beim Definieren der Tabelle:

CREATE TABLE Customers(
  id INTEGER PRIMARY KEY,
  name TEXT,
  username TEXT UNIQUE
);

SQLite tut dasselbe wie:

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 Aufwand als Kotlin oder Java Code. Es empfiehlt sich, anstelle von App-Code SQLite zu verwenden.

Mehrere Einfügungen in einer einzigen Transaktion im Batch ausführen

Mit einer Transaktion werden mehrere Vorgänge verbindlich ausgeführt, was nicht nur die Effizienz, sondern auch die Korrektheit verbessert. Um die Datenkonsistenz und und die Leistung zu beschleunigen, können Sie das Einfügen im Batch tun:

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, um die Leistung zu messen.

Interaktiven Prompt von SQLite verwenden

SQLite auf Ihrem Computer ausführen, um Abfragen auszuführen und Neues zu lernen Verschiedene Android-Plattformversionen verwenden unterschiedliche Versionen von SQLite. Wenn Sie dieselbe Engine wie auf einem Android-Gerät verwenden möchten, verwenden Sie adb shell und führen Sie sqlite3 auf dem Zielgerät aus.

Sie können SQLite bitten, Abfragen zu zeitlich zu erfassen:

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, indem Sie die 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 aus Paris zu finden. Dies wird als lineare Komplexität bezeichnet. SQLite muss lesen alle Zeilen und behalten nur die Zeilen bei, die mit Kunden aus Paris übereinstimmen. Um das Problem zu beheben können 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 bitten, Abfragepläne:

sqlite> .eqp on

Weitere Informationen finden Sie unter Abfrageplanung.

SQLite-Analysetool

SQLite bietet sqlite3_analyzer Befehlszeile verwenden, um zusätzliche Informationen auszulesen, die für Probleme mit der Leistung zu beheben. Zur Installation besuche die SQLite-Downloadseite

Mit adb pull können Sie eine Datenbankdatei von einem Zielgerät auf Ihr Workstation für die Analyse:

adb pull /data/data/<app_package_name>/databases/<db_name>.db

SQLite Browser

Sie können auch das GUI-Tool installieren, SQLite Browser auf SQLite Downloadseite.

Android-Logging

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

### Perfetto tracing {:#perfetto-tracing}

When [configuring Perfetto](https://perfetto.dev/docs/concepts/config), you may
add the following to include tracks for individual queries:

```protobuf
data_sources {
  config {
    name: "linux.ftrace"
    ftrace_config {
      atrace_categories: "database"
    }
  }
}