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.MINlubMAX: określa najniższą lub najwyższą wartość. Działa w przypadku kolumn liczbowych, typówDATEi 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óżpgszprzezdbsz.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ę przezpgsz.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
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Uruchamianie testów porównawczych w trybie ciągłej integracji
- Zablokowane klatki
- Tworzenie i pomiar profili podstawowych bez użycia biblioteki Macrobenchmark