Sprawdzone metody dotyczące wydajności SQLite

Android ma wbudowaną obsługę SQLite, czyli wydajnej bazy danych SQL. Aby zoptymalizować wydajność aplikacji, stosuj te sprawdzone metody. Dzięki temu aplikacja będzie działać szybko i przewidywalnie szybko, nawet gdy ilość danych będzie się zwiększać. Stosując te sprawdzone metody, zmniejszasz też prawdopodobieństwo wystąpienia problemów z wydajnością, które są trudne do odtworzenia i rozwiązania.

Aby zwiększyć wydajność, postępuj zgodnie z tymi zasadami:

  • Odczytuj mniej wierszy i kolumn: zoptymalizuj zapytania, aby pobierać tylko niezbędne dane. Zminimalizuj ilość danych odczytywanych z bazy danych, ponieważ nadmierne pobieranie danych może wpływać na wydajność.

  • Przenieś pracę do silnika SQLite: wykonuj obliczenia, filtrowanie i sortowanie w zapytaniach SQL. Korzystanie z silnika zapytań SQLite może znacznie zwiększyć wydajność.

  • Zmodyfikuj schemat bazy danych: zaprojektuj schemat bazy danych tak, aby ułatwić SQLite tworzenie wydajnych planów zapytań i reprezentacji danych. Aby zwiększyć wydajność, odpowiednio indeksuj tabele i optymalizuj ich struktury.

Możesz też użyć dostępnych narzędzi do rozwiązywania problemów, aby zmierzyć wydajność bazy danych SQLite i zidentyfikować obszary wymagające optymalizacji.

Zalecamy używanie biblioteki Jetpack Room.

Konfigurowanie bazy danych pod kątem wydajności

Aby skonfigurować bazę danych pod kątem optymalnej wydajności w SQLite, wykonaj czynności opisane w tej sekcji.

Włączanie logowania Write-Ahead

SQLite implementuje mutacje, dołączając je do dziennika, który od czasu do czasu kompresuje do bazy danych. Nazywa się to logowaniem Write-Ahead (WAL).

Włącz WAL chyba że używasz ATTACH DATABASE.

Zmniejszanie rygoru trybu synchronizacji

Gdy używasz WAL, domyślnie każde zatwierdzenie wysyła polecenie fsync, aby zapewnić zapisanie danych na dysku. Zwiększa to trwałość danych, ale spowalnia zatwierdzanie.

SQLite ma opcję sterowania trybem synchronicznym. Jeśli włączysz WAL, ustaw tryb synchroniczny na NORMAL:

// When opening the database
val paramsBuilder: SQLiteDatabase.OpenParams.Builder = SQLiteDatabase.OpenParams.Builder()
paramsBuilder.journalMode = SQLiteDatabase.SYNC_MODE_NORMAL

// Or: after having opened the database
db.execSQL("PRAGMA synchronous = NORMAL");

W tym ustawieniu zatwierdzenie może zostać zwrócone, zanim dane zostaną zapisane na dysku. Jeśli nastąpi wyłączenie urządzenia, np. z powodu utraty zasilania lub paniki jądra, zatwierdzone dane mogą zostać utracone. Jednak dzięki logowaniu baza danych nie zostanie uszkodzona.

Jeśli awarii ulegnie tylko Twoja aplikacja, dane nadal będą zapisywane na dysku. W przypadku większości aplikacji to ustawienie zwiększa wydajność bez ponoszenia dodatkowych kosztów.

Definiowanie wydajnych schematów tabel

Aby zoptymalizować wydajność i zminimalizować zużycie danych, zdefiniuj wydajny schemat tabeli. SQLite tworzy wydajne plany zapytań i dane, co przyspiesza pobieranie danych. W tej sekcji znajdziesz sprawdzone metody tworzenia schematów tabel.

Rozważ użycie INTEGER PRIMARY KEY

W tym przykładzie zdefiniuj i wypełnij tabelę w ten sposób:

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');

Dane wyjściowe tabeli są takie:

rowid id name city
1 456 John Lennon Liverpool, England
2 123 Michael Jackson Gary, IN
3 789 Dolly Parton Sevier County, TN

Kolumna rowid to indeks, który zachowuje kolejność wstawiania. Zapytania, które filtrują według rowid, są implementowane jako szybkie wyszukiwanie w drzewie B, ale zapytania, które filtrują według id, są powolnym skanowaniem tabeli.

Jeśli planujesz wyszukiwanie według id, możesz uniknąć przechowywania kolumny rowid, co pozwoli zmniejszyć ilość danych w pamięci masowej i przyspieszyć działanie bazy danych:

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

Twoja tabela będzie teraz wyglądać tak:

id name city
123 Michael Jackson Gary, IN
456 John Lennon Liverpool, England
789 Dolly Parton Sevier County, TN

Ponieważ nie musisz przechowywać kolumny rowid, zapytania id są szybkie. Pamiętaj, że tabela jest teraz sortowana na podstawie id, a nie kolejności wstawiania.

Przyspieszanie zapytań za pomocą indeksów

SQLite używa indeksów do przyspieszania zapytań. Jeśli filtrujesz (WHERE), sortujesz (ORDER BY) lub agregujesz (GROUP BY) kolumnę, a tabela ma indeks dla tej kolumny, zapytanie jest przyspieszane.

W poprzednim przykładzie filtrowanie według city wymaga skanowania całej tabeli:

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

W przypadku aplikacji z dużą liczbą zapytań dotyczących miast możesz przyspieszyć te zapytania za pomocą indeksu:

CREATE INDEX city_index ON Customers(city);

Indeks jest implementowany jako dodatkowa tabela, posortowana według kolumny indeksu i mapowana na rowid:

city rowid
Gary, IN 2
Liverpool, England 1
Sevier County, TN 3

Pamiętaj, że koszt przechowywania kolumny city jest teraz podwójny, ponieważ jest ona obecna zarówno w oryginalnej tabeli, jak i w indeksie. Ponieważ używasz indeksu, koszt dodatkowego miejsca na dane jest wart korzyści wynikających z szybszych zapytań. Nie utrzymuj jednak indeksu, którego nie używasz, aby uniknąć płacenia za miejsce na dane bez zwiększania wydajności zapytań.

Tworzenie indeksów wielokolumnowych

Jeśli Twoje zapytania łączą wiele kolumn, możesz utworzyć indeksy wielokolumnowe aby w pełni przyspieszyć zapytanie. Możesz też użyć indeksu w kolumnie zewnętrznej i pozwolić na wykonanie wyszukiwania wewnętrznego jako skanowania liniowego.

Na przykład w przypadku tego zapytania:

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

Możesz przyspieszyć zapytanie za pomocą indeksu wielokolumnowego w tej samej kolejności co w zapytaniu:

CREATE INDEX city_name_index ON Customers(city, name);

Jeśli jednak masz tylko indeks w kolumnie city, sortowanie zewnętrzne jest nadal przyspieszane, a sortowanie wewnętrzne wymaga skanowania liniowego.

Działa to również w przypadku zapytań z prefiksem. Na przykład indeks ON Customers (city, name) przyspiesza też filtrowanie, sortowanie i grupowanie według city, ponieważ tabela indeksu dla indeksu wielokolumnowego jest uporządkowana według podanych indeksów w podanej kolejności.

Rozważ użycie WITHOUT ROWID

Domyślnie SQLite tworzy dla Twojej tabeli kolumnę rowid, gdzie rowid jest niejawnym INTEGER PRIMARY KEY AUTOINCREMENT. Jeśli masz już kolumnę INTEGER PRIMARY KEY, staje się ona aliasem rowid.

W przypadku tabel, które mają klucz podstawowy inny niż INTEGER lub złożony z kolumn, rozważ użycie WITHOUT ROWID.

Przechowywanie małych danych jako BLOB, a dużych jako pliku

Jeśli chcesz powiązać duże dane z wierszem, np. miniaturę obrazu lub zdjęcie kontaktu, możesz przechowywać dane w kolumnie BLOB lub w pliku, a następnie zapisać ścieżkę w kolumnie.

Rozmiar plików jest zwykle zaokrąglany do wielokrotności 4 KB. W przypadku bardzo małych plików, w których błąd zaokrąglenia jest znaczny, bardziej efektywne jest przechowywanie ich w bazie danych jako BLOB. SQLite minimalizuje wywołania systemu plików i jest szybszy niż podstawowy system plików w niektórych przypadkach.

Zwiększanie wydajności zapytań

Aby zwiększyć wydajność zapytań w SQLite, stosuj te sprawdzone metody, które pozwolą Ci zminimalizować czasy odpowiedzi i zmaksymalizować wydajność przetwarzania.

Odczytuj tylko potrzebne wiersze

Filtry pozwalają zawęzić wyniki przez określenie określonych kryteriów, takich jak zakres dat, lokalizacja lub nazwa. Limity pozwalają kontrolować liczbę wyświetlanych wyników:

db.rawQuery("""
    SELECT name
    FROM Customers
    LIMIT 10;
    """.trimIndent(),
    null
).use { cursor ->
    while (cursor.moveToNext()) {
        // Process cursor data
    }
}

Odczytuj tylko potrzebne kolumny

Unikaj wybierania niepotrzebnych kolumn, które mogą spowolnić zapytania i marnować zasoby. Zamiast tego wybieraj tylko używane kolumny.

W tym przykładzie wybierasz id, name i phone:

// 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)
        // Further processing
    }
}

Potrzebujesz jednak tylko kolumny name:

db.rawQuery("""
    SELECT name
    FROM Customers;
    """.trimIndent(),
    null
).use { cursor ->
    while (cursor.moveToNext()) {
        val name = cursor.getString(0)
        // Further processing
    }
}

Parametryzowanie zapytań

Ciąg zapytania może zawierać parametr, który jest znany tylko w czasie działania, np.:

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

W powyższym kodzie każde zapytanie tworzy inny ciąg znaków, dlatego nie korzysta z pamięci podręcznej instrukcji. Każde wywołanie wymaga, aby SQLite skompilowało je przed wykonaniem. Zamiast tego możesz zastąpić argument id parametrem i powiązać wartość za pomocą selectionArgs:

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

Teraz zapytanie można skompilować i zapisać w pamięci podręcznej. Skompilowane zapytanie jest ponownie używane między różnymi wywołaniami getNameById(long).

Iteracja w SQL, a nie w kodzie

Używaj jednego zapytania, które zwraca wszystkie docelowe wyniki, zamiast pętli programowej iterującej po zapytaniach SQL w celu zwracania poszczególnych wyników. Pętla programowa jest około 1000 razy wolniejsza niż pojedyncze zapytanie SQL.

Używaj DISTINCT w przypadku unikalnych wartości

Użycie słowa kluczowego DISTINCT może zwiększyć wydajność zapytań, ponieważ zmniejsza ilość danych, które trzeba przetworzyć. Jeśli na przykład chcesz zwrócić tylko unikalne wartości z kolumny, użyj DISTINCT:

db.rawQuery("""
    SELECT DISTINCT name
    FROM Customers;
    """.trimIndent(),
    null
).use { cursor ->
    while (cursor.moveToNext()) {
        // Only iterate over distinct names in Kotlin
        // Process distinct name
    }
}

W miarę możliwości używaj funkcji agregacji

Używaj funkcji agregacji w przypadku wyników zbiorczych bez danych wierszy. Na przykład ten kod sprawdza, czy istnieje co najmniej 1 pasujący wiersz:

// 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
        // Handle found
    } else {
        // No customers from Paris
        // Handle not found
}

Aby pobrać tylko pierwszy wiersz, możesz użyć EXISTS(), aby zwrócić 0, jeśli nie ma pasującego wiersza, i 1, jeśli pasuje co najmniej 1 wiersz:

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
        // Handle found
    } else {
        // No customers from Paris
        // Handle not found
    }
}

Używaj funkcji agregacji SQLite w kodzie aplikacji:

  • COUNT: zlicza liczbę wierszy w kolumnie.
  • SUM: dodaje wszystkie wartości liczbowe w kolumnie.
  • MIN lub MAX: określa najniższą lub najwyższą wartość. Działa w przypadku kolumn liczbowych, typów DATE i typów tekstowych.
  • AVG: znajduje średnią wartość liczbową.
  • GROUP_CONCAT: łączy ciągi znaków z opcjonalnym separatorem.

Używaj COUNT() zamiast Cursor.getCount()

W tym przykładzie funkcja Cursor.getCount() odczytuje wszystkie wiersze z bazy danych i zwraca wszystkie wartości wierszy:

// 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()
    // Use count
}

Jednak w przypadku użycia COUNT() baza danych zwraca tylko liczbę:

db.rawQuery("""
    SELECT COUNT(*)
    FROM Customers;
    """.trimIndent(),
    null
).use { cursor ->
    cursor.moveToFirst()
    val count = cursor.getInt(0)
    // Use count
}

Zagnieżdżaj zapytania zamiast kodu

SQL jest kompozycyjny i obsługuje podzapytania, złączenia i ograniczenia klucza obcego. Możesz użyć wyniku jednego zapytania w innym zapytaniu bez przechodzenia przez kod aplikacji. Zmniejsza to potrzebę kopiowania danych z SQLite i pozwala silnikowi bazy danych zoptymalizować zapytanie.

W tym przykładzie możesz uruchomić zapytanie, aby sprawdzić, które miasto ma najwięcej klientów, a następnie użyć wyniku w innym zapytaniu, aby znaleźć wszystkich klientów z tego miasta:

// 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()) {
                // Process inner cursor data
            }
        }
    }
}

Aby uzyskać wynik w połowie czasu poprzedniego przykładu, użyj jednego zapytania SQL z zagnieżdżonymi instrukcjami:

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()) {
        // Process cursor data
    }
}

Sprawdzanie unikalności w SQL

Jeśli wiersz nie może zostać wstawiony, chyba że wartość określonej kolumny jest unikalna w tabeli, bardziej efektywne może być wymuszenie tej unikalności jako ograniczenia kolumny.

W tym przykładzie uruchamiane jest 1 zapytanie, aby sprawdzić poprawność wiersza do wstawienia, a drugie, aby go wstawić:

// 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
    )
)

Zamiast sprawdzać ograniczenie unikalności w Kotlinie, możesz sprawdzić je w SQL podczas definiowania tabeli:

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

SQLite robi to samo co:

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

Teraz możesz wstawić wiersz i pozwolić SQLite sprawdzić ograniczenie:

try {
    db.execSql(
        "INSERT INTO Customers VALUES (?, ?, ?)",
        arrayOf(customer.id.toString(), customer.name, customer.username)
    )
} catch(e: SQLiteConstraintException) {
    throw AddCustomerException(customer, e)
}

SQLite obsługuje unikalne indeksy z wieloma kolumnami:

CREATE TABLE table(...);
CREATE UNIQUE INDEX unique_table ON table(column1, column2, ...);

SQLite sprawdza ograniczenia szybciej i z mniejszym narzutem niż kod Kotlin. Sprawdzoną metodą jest używanie SQLite zamiast kodu aplikacji.

Zbiorcze wstawianie w ramach jednej transakcji

Transakcja zatwierdza wiele operacji, co zwiększa nie tylko wydajność, ale też poprawność. Aby zwiększyć spójność danych i przyspieszyć działanie, możesz wstawiać dane zbiorczo:

db.beginTransaction()
try {
    customers.forEach { customer ->
        db.execSql(
            "INSERT INTO Customers VALUES (?, ?, ?)",
            arrayOf(customer.id.toString(), customer.name, "customerValue")
        )
    }
} finally {
    db.endTransaction()
}

Korzystanie z narzędzi do rozwiązywania problemów

SQLite udostępnia te narzędzia do rozwiązywania problemów, które pomagają mierzyć wydajność.

Korzystanie z interaktywnego wiersza poleceń SQLite

Uruchom SQLite na swoim komputerze, aby wykonywać zapytania i się uczyć. Różne wersje platformy Android używają różnych wersji SQLite. Aby używać tego samego silnika co na urządzeniu z Androidem, użyj adb shell i uruchom sqlite3 na urządzeniu docelowym.

Możesz poprosić SQLite o zmierzenie czasu zapytań:

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

EXPLAIN QUERY PLAN

Możesz poprosić SQLite o wyjaśnienie, jak zamierza odpowiedzieć na zapytanie, używając EXPLAIN QUERY PLAN:

sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SCAN Customers

Poprzedni przykład wymaga pełnego skanowania tabeli bez indeksu, aby znaleźć wszystkich klientów z Paryża. Nazywa się to złożonością liniową. SQLite musi odczytać wszystkie wiersze i zachować tylko te, które pasują do klientów z Paryża. Aby to naprawić, możesz dodać indeks:

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=?

Jeśli używasz interaktywnej powłoki, możesz poprosić SQLite o zawsze wyjaśnianie planów zapytań:

sqlite> .eqp on

Więcej informacji znajdziesz w artykule Planowanie zapytań.

Analizator SQLite

SQLite udostępnia interfejs wiersza poleceń sqlite3_analyzer, który umożliwia zrzucanie dodatkowych informacji, które można wykorzystać do rozwiązywania problemów z wydajnością. Aby zainstalować, otwórz stronę pobierania SQLite.

Aby pobrać plik bazy danych z urządzenia docelowego na stację roboczą do analizy, możesz użyć polecenia adb pull:

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

Przeglądarka SQLite

Na stronie pobierania SQLite możesz też zainstalować narzędzie GUI SQLite Browser.

Logowanie w Androidzie

Android mierzy czas zapytań SQLite i rejestruje je:

# Enable query time logging
$ adb shell setprop log.tag.SQLiteTime VERBOSE
# Disable query time logging
$ adb shell setprop log.tag.SQLiteTime ERROR

Śledzenie Perfetto

Podczas konfigurowania Perfetto możesz dodać te elementy, aby uwzględnić ścieżki dla poszczególnych zapytań:

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

dumpsys meminfo

adb shell dumpsys meminfo <package-name> wyświetli statystyki dotyczące wykorzystania pamięci przez aplikację, w tym szczegóły dotyczące pamięci SQLite. Na przykład te dane zostały pobrane z danych wyjściowych adb shell dumpsys meminfo com.google.android.gms.persistent na urządzeniu programisty:

DATABASES
      pgsz     dbsz   Lookaside(b) cache hits cache misses cache size  Dbname
PER CONNECTION STATS
         4       52             45     8    41     6  /data/user/10/com.google.android.gms/databases/gaia-discovery
         4        8                    0     0     0    (attached) temp
         4       52             56     5    23     6  /data/user/10/com.google.android.gms/databases/gaia-discovery (1)
         4      252             95   233   124    12  /data/user_de/10/com.google.android.gms/databases/phenotype.db
         4        8                    0     0     0    (attached) temp
         4      252             17     0    17     1  /data/user_de/10/com.google.android.gms/databases/phenotype.db (1)
         4     9280            105 103169 69805    25  /data/user/10/com.google.android.gms/databases/phenotype.db
         4       20                    0     0     0    (attached) temp
         4     9280            108 13877  6394    25  /data/user/10/com.google.android.gms/databases/phenotype.db (2)
         4        8                    0     0     0    (attached) temp
         4     9280            105 12548  5519    25  /data/user/10/com.google.android.gms/databases/phenotype.db (3)
         4        8                    0     0     0    (attached) temp
         4     9280            107 18328  7886    25  /data/user/10/com.google.android.gms/databases/phenotype.db (1)
         4        8                    0     0     0    (attached) temp
         4       36             51   156    29     5  /data/user/10/com.google.android.gms/databases/mobstore_gc_db_v0
         4       36             97    47    27    10  /data/user/10/com.google.android.gms/databases/context_feature_default.db
         4       36             56     3    16     4  /data/user/10/com.google.android.gms/databases/context_feature_default.db (2)
         4      300             40  2111    24     5  /data/user/10/com.google.android.gms/databases/gservices.db
         4      300             39     3    17     4  /data/user/10/com.google.android.gms/databases/gservices.db (1)
         4       20             17     0    14     1  /data/user/10/com.google.android.gms/databases/gms.notifications.db
         4       20             33     1    15     2  /data/user/10/com.google.android.gms/databases/gms.notifications.db (1)
         4      120             40   143   163     4  /data/user/10/com.google.android.gms/databases/android_pay
         4      120            123    86    32    19  /data/user/10/com.google.android.gms/databases/android_pay (1)
         4       28             33     4    17     3  /data/user/10/com.google.android.gms/databases/googlesettings.db
POOL STATS
     cache hits  cache misses    cache size  Dbname
             13            68            81  /data/user/10/com.google.android.gms/databases/gaia-discovery
            233           145           378  /data/user_de/10/com.google.android.gms/databases/phenotype.db
         147921         89616        237537  /data/user/10/com.google.android.gms/databases/phenotype.db
            156            30           186  /data/user/10/com.google.android.gms/databases/mobstore_gc_db_v0
             50            57           107  /data/user/10/com.google.android.gms/databases/context_feature_default.db
           2114            43          2157  /data/user/10/com.google.android.gms/databases/gservices.db
              1            31            32  /data/user/10/com.google.android.gms/databases/gms.notifications.db
            229           197           426  /data/user/10/com.google.android.gms/databases/android_pay
              4            18            22  /data/user/10/com.google.android.gms/databases/googlesettings.db

W sekcji DATABASES znajdziesz:

  • pgsz: rozmiar jednej strony bazy danych w KB.
  • dbsz: rozmiar całej bazy danych w stronach. Aby uzyskać rozmiar w KB, pomnóż pgsz przez dbsz.
  • Lookaside(b): pamięć przydzielona do bufora lookaside SQLite na połączenie w bajtach. Zwykle są to bardzo małe wartości.
  • cache hits: SQLite utrzymuje pamięć podręczną stron bazy danych. Jest to liczba trafień w pamięci podręcznej stron (liczba).
  • cache misses: liczba nietrafień w pamięci podręcznej stron (liczba).
  • cache size: liczba stron w pamięci podręcznej (liczba). Aby uzyskać rozmiar w KB, pomnóż tę liczbę przez pgsz.
  • Dbname: ścieżka do pliku bazy danych. W naszym przykładzie niektóre bazy danych mają na końcu nazwy (1) lub inną liczbę, co oznacza, że istnieje więcej niż 1 połączenie z tą samą bazą danych. Statystyki są śledzone na połączenie.

W sekcji POOL STATS znajdziesz:

  • cache hits: SQLite zapisuje w pamięci podręcznej przygotowane instrukcje i próbuje ich ponownie użyć podczas uruchamiania zapytań, aby zaoszczędzić trochę pracy i pamięci podczas kompilowania instrukcji SQL. Jest to liczba trafień w pamięci podręcznej instrukcji (liczba).
  • cache misses: liczba nietrafień w pamięci podręcznej instrukcji (liczba).
  • cache size: od Androida 17 ta wartość zawiera łączną liczbę przygotowanych instrukcji w pamięci podręcznej. W starszych wersjach ta wartość jest równa sumie trafień i nietrafień wymienionych w pozostałych 2 kolumnach, i nie reprezentuje rozmiaru pamięci podręcznej.

Dodatkowe materiały

Wyświetlanie treści