Best Practices für die SQLite-Leistung

Android bietet integrierte Unterstützung für SQLite, eine effiziente SQL-Datenbank. Befolgen Sie diese Best Practices, um die Leistung Ihrer Anwendung zu optimieren und dafür zu sorgen, dass sie auch beim Anwachsen Ihrer Daten schnell und vorhersehbar schnell bleibt. Mit diesen Best Practices verringern Sie auch die Wahrscheinlichkeit von Leistungsproblemen, die schwer zu reproduzieren und zu beheben sind.

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

  • Weniger Zeilen und Spalten lesen: Optimieren Sie Ihre Abfragen so, dass nur die erforderlichen Daten abgerufen werden. Minimieren Sie die Menge der aus der Datenbank gelesenen Daten, da ein zu vieler Datenabrufe die Leistung beeinträchtigen können.

  • Arbeiten in SQLite-Engine übertragen: Führen Sie Berechnungen, Filterung und Sortiervorgänge innerhalb der SQL-Abfragen aus. Die Verwendung der Abfrage-Engine von SQLite kann die Leistung erheblich verbessern.

  • Datenbankschema ändern: Entwerfen Sie Ihr Datenbankschema, damit SQLite effiziente Abfragepläne und Datendarstellungen erstellen kann. Indexieren Sie Tabellen ordnungsgemäß und optimieren Sie die Tabellenstrukturen, um die Leistung zu verbessern.

Darüber hinaus können Sie die verfügbaren Tools zur Fehlerbehebung verwenden, um die Leistung Ihrer SQLite-Datenbank zu messen und Bereiche zu ermitteln, die optimiert werden müssen.

Wir empfehlen die Verwendung der Jetpack Room Library.

Datenbank für Leistung konfigurieren

Führen Sie die Schritte in diesem Abschnitt aus, um Ihre Datenbank für optimale Leistung in SQLite zu konfigurieren.

Write-Ahead-Logging aktivieren

SQLite implementiert Mutationen, indem es sie an ein Log anhängt, wodurch gelegentlich 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 Verwendung von WAL wird standardmäßig bei jedem Commit ein fsync ausgegeben, um dafür zu sorgen, dass die Daten das Laufwerk erreichen. Dies verbessert die Langlebigkeit der Daten, verlangsamt jedoch Ihre Commits.

SQLite bietet eine Option zur Steuerung 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 wurden. Wenn ein Gerät heruntergefahren wird, z. B. bei einem Stromausfall oder einer Kernel-Panic, gehen die übergebenen Daten möglicherweise verloren. Aufgrund des Loggings ist Ihre Datenbank jedoch nicht beschädigt.

Wenn nur Ihre App abstürzt, erreichen Ihre Daten trotzdem das Laufwerk. Bei den meisten Anwendungen führt diese Einstellung zu Leistungsverbesserungen, ohne dass erhebliche Kosten anfallen.

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, die zu einem schnelleren Datenabruf führen. Dieser Abschnitt enthält Best Practices zum Erstellen von Tabellenschemas.

INTEGER PRIMARY KEY verwenden

Definieren und füllen Sie für dieses Beispiel eine Tabelle wie folgt:

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:

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 ein Index, bei dem der Anzeigenauftrag beibehalten wird. Abfragen, die nach rowid filtern, werden als schnelle Suche im B-Baum implementiert. Abfragen, die nach id filtern, sind jedoch ein langsamer Tabellenscan.

Wenn Sie Suchvorgänge nach id ausführen möchten, können Sie das Speichern der Spalte rowid vermeiden, um weniger Daten zu speichern und eine insgesamt schnellere Datenbank zu erhalten:

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

Die 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. Die Tabelle ist jetzt nach id und nicht mehr nach Anzeigenauftrag sortiert.

Abfragen mit Indexen beschleunigen

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

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 Städteabfragen 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 Zeilen-ID
Gary, IN 2
Liverpool, England 1
Sevier County, Tennessee 3

Die Speicherkosten für die Spalte city sind jetzt doppelt so hoch, weil sie jetzt sowohl in der ursprünglichen Tabelle als auch im Index vorhanden ist. Da Sie den Index verwenden, sind die Kosten für zusätzlichen Speicher den Vorteil schnellerer Abfragen wert. Verwalten Sie jedoch keinen Index, den Sie nicht verwenden, um zu vermeiden, dass Sie die Speicherkosten bezahlen, ohne dass die Abfrageleistung gesteigert wird.

Mehrspaltige Indexe erstellen

Wenn in Ihren Abfragen mehrere Spalten kombiniert werden, können Sie mehrspaltige Indexe erstellen, um die Abfrage vollständig zu beschleunigen. Sie können auch einen Index für eine externe Spalte verwenden und die interne Suche als linearen Scan durchführen lassen.

Angenommen, die folgende Abfrage würde lauten:

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 angeben beschleunigen:

CREATE INDEX city_name_index ON Customers(city, name);

Wenn Sie jedoch nur einen Index für city haben, wird die äußere Reihenfolge dennoch beschleunigt, während die interne Reihenfolge einen linearen Scan erfordert.

Dies funktioniert auch mit Präfixanfragen. Ein Index-ON Customers (city, name) beschleunigt zum Beispiel auch das Filtern, Sortieren und Gruppieren nach city, da die Indextabelle für einen mehrspaltigen Index nach den angegebenen Indexen in der angegebenen Reihenfolge geordnet wird.

WITHOUT ROWID verwenden

Standardmäßig erstellt SQLite eine rowid-Spalte für die Tabelle, wobei rowid ein impliziter INTEGER PRIMARY KEY AUTOINCREMENT-Wert ist. Wenn Sie bereits eine Spalte mit 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 aus mehreren Spalten zusammengesetzten Primärschlüssel kann WITHOUT ROWID verwendet werden.

Speichern Sie kleine Daten als BLOB und große Daten als Datei.

Wenn Sie einer Zeile große Daten zuordnen möchten, z. B. eine Miniaturansicht eines Bildes oder ein Foto für einen Kontakt, können Sie die Daten entweder in einer BLOB-Spalte oder in einer Datei und dann den Dateipfad in der Spalte speichern.

Dateien werden in der Regel auf 4-KB-Schritte aufgerundet. Bei sehr kleinen Dateien, bei denen der Rundungsfehler erheblich ist, sollten sie effizienter als BLOB in der Datenbank gespeichert werden. SQLite minimiert Dateisystemaufrufe und ist in einigen Fällen schneller als das zugrunde liegende Dateisystem.

Abfrageleistung verbessern

Befolgen Sie diese Best Practices, um die Abfrageleistung in SQLite zu 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 angeben, z. B. Zeitraum, Ort oder Name. Mit Limits können Sie die Anzahl der angezeigten Ergebnisse festlegen:

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, da dies Ihre Abfragen verlangsamen und Ressourcen verschwenden kann. Wählen Sie stattdessen nur verwendete Spalten aus.

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);
    ...
  }
}

Verwenden Sie DISTINCT für eindeutige Werte

Mit dem Schlüsselwort DISTINCT können Sie die Leistung Ihrer Abfragen verbessern, da weniger Daten verarbeitet werden müssen. 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
    ...
  }
}

Nach Möglichkeit Aggregatfunktionen verwenden

Aggregatfunktionen verwenden, um Ergebnisse ohne Zeilendaten zu aggregieren Mit dem folgenden Code wird beispielsweise geprüft, ob es mindestens eine übereinstimmende Zeile gibt:

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 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 die SQLite-Aggregatfunktionen im App-Code:

  • COUNT: zählt, wie viele Zeilen sich in einer Spalte befinden.
  • SUM: addiert alle numerischen Werte in einer Spalte.
  • MIN oder MAX: Legt den niedrigsten oder höchsten Wert fest. Funktioniert für numerische Spalten, DATE-Typen und Texttypen.
  • AVG: ermittelt den numerischen Durchschnittswert.
  • GROUP_CONCAT: verkettet Strings mit einem optionalen Trennzeichen.

COUNT() statt 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();
  ...
}

Bei Verwendung von COUNT() gibt die Datenbank jedoch 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 Anwendungscode zu durchlaufen. Dies reduziert die Notwendigkeit, Daten aus SQLite zu kopieren, und lässt die Datenbank-Engine Ihre Abfrage optimieren.

Im folgenden Beispiel können Sie eine Abfrage ausführen, um zu ermitteln, welche Stadt die meisten Kunden hat. Das Ergebnis können Sie dann 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()) {
          ...
        }
    }
  }
}

Um das Ergebnis in der Hälfte der Fälle des vorherigen Beispiels zu erhalten, 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, kann es effizienter sein, 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 beim Definieren der Tabelle auch in SQL prüfen:

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

SQLite funktioniert so:

CREATE TABLE Customers(...);
CREATE UNIQUE INDEX CustomersUsername ON Customers(username);

Jetzt können Sie eine Zeile einfügen und SQLite die Einschränkung überprü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, SQLite anstelle von App-Code zu verwenden.

Mehrere Einfügungen in einer einzigen Transaktion im Batch hinzufügen

Eine Transaktion übernimmt mehrere Vorgänge, was nicht nur die Effizienz, sondern auch die Richtigkeit verbessert. Um die Datenkonsistenz zu verbessern und die Leistung zu beschleunigen, können Sie Einfügungen im Batch ausführen:

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.

Interaktive Aufforderung von SQLite verwenden

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

Sie können SQLite zur Zeitabfrage verwenden:

sqlite> .timer on
sqlite> SELECT ...
Run Time: real ... user ... sys ...

EXPLAIN QUERY PLAN

Sie können SQLite bitten, mit EXPLAIN QUERY PLAN zu erklären, wie eine Abfrage beantwortet werden soll:

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 Zeilen beibehalten, die mit Kunden aus Paris übereinstimmen. Um dies 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, immer Abfragepläne zu erklären:

sqlite> .eqp on

Weitere Informationen finden Sie unter Abfrageplanung.

SQLite-Analysetool

SQLite bietet die sqlite3_analyzer-Befehlszeile zum Dump von zusätzlichen Informationen, die zur Fehlerbehebung bei der Leistung verwendet werden können. Rufen Sie zur Installation die SQLite-Downloadseite auf.

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 das GUI-Tool SQLite-Browser auch über die SQLite-Downloadseite installieren.

Android-Protokollierung

Android verwendet SQLite-Abfragen und protokolliert sie für dich:

# 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"
    }
  }
}