Android oferuje wbudowane wsparcie dla SQLite, czyli wydajnej bazy danych SQL. Stosuj te sprawdzone metody, aby zoptymalizować wydajność aplikacji i utrzymać jej szybkość i stabilność wraz ze wzrostem ilości danych. Stosując te sprawdzone metody, zmniejszysz też ryzyko wystąpienia problemów z wydajnością, które są trudne do odtworzenia i rozwiązania.
Aby uzyskać większą wydajność, przestrzegaj tych zasad:
Czytanie mniejszej liczby 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łynąć na wydajność.
Przesyłanie pracy do mechanizmu SQLite: wykonywanie obliczeń, filtrowania i sortowania w ramach zapytań SQL. Korzystanie z silnika zapytań SQLite może znacznie poprawić wydajność.
Zmień schemat bazy danych: zaprojektuj schemat bazy danych, aby ułatwić SQLite tworzenie wydajnych planów zapytań i reprezentacji danych. Prawidłowo indeksuj tabele i optymalizuj ich struktury, aby zwiększyć wydajność.
Możesz też użyć dostępnych narzędzi do rozwiązywania problemów, aby zmierzyć wydajność bazy danych SQLite i wyznaczyć obszary wymagające optymalizacji.
Zalecamy użycie 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 z zapisywaniem z wyprzedzeniem
SQLite wdraża mutacje, dołączając je do dziennika, który okresowo jest kompresowany w bazie danych. Nazywa się to zapisem z wyprzedzeniem (WAL).
Włącz WAL, chyba że używasz ATTACH
DATABASE
.
Zwolnij tryb synchronizacji
Podczas używania WAL domyślnie każda zmiana powoduje wysłanie fsync
, aby dane dotarły na dysk. Zwiększa to trwałość danych, ale spowalnia procesy zatwierdzania.
SQLite ma opcję sterowania trybem synchronicznym. Jeśli włączysz WAL, ustaw tryb synchronizacji na NORMAL
:
Kotlin
db.execSQL("PRAGMA synchronous = NORMAL")
Java
db.execSQL("PRAGMA synchronous = NORMAL");
Przy takim ustawieniu powiadomienie o zmianie może zostać zwrócone, zanim dane zostaną zapisane na dysku. Jeśli urządzenie zostanie wyłączone (np. z powodu utraty zasilania lub paniki jądra), zapisane dane mogą zostać utracone. Jednak dzięki rejestrowaniu Twoja baza danych nie jest uszkodzona.
Jeśli tylko aplikacja ulegnie awarii, dane nadal będą przesyłane na dysk. W przypadku większości aplikacji to ustawienie zapewnia poprawę wydajności bez dodatkowych kosztów.
Definiowanie wydajnych schematów tabeli
Aby zoptymalizować wydajność i zmniejszyć zużycie danych, zdefiniuj wydajny schemat tabeli. SQLite tworzy efektywne plany zapytań i dane, co prowadzi do szybszego pobierania danych. W tej sekcji znajdziesz sprawdzone metody tworzenia schematów tabel.
Rozważ INTEGER PRIMARY KEY
W tym przykładzie zdefiniuj tabelę i utwórz w niej dane 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 wyglądają tak:
rowid | id | nazwa | miasto |
---|---|---|---|
1 | 456 | John Lennon | Liverpool, Anglia |
2 | 123 | Michael Jackson | Gary, Indiana |
3 | 789 | Dolly Parton | Hrabstwo Sevier, Tennessee |
Kolumna rowid
to indeks, który zachowuje kolejność wstawiania. Zapytania z filtrem rowid
są implementowane jako szybkie wyszukiwanie w drzewie B, ale zapytania z filtrem id
są skanowane powoli.
Jeśli planujesz wyszukiwanie według wartości id
, możesz zrezygnować z przechowywania kolumny rowid
, aby zaoszczędzić miejsce na dane i przyspieszyć działanie bazy danych:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
city TEXT
);
Tabela wygląda teraz tak:
id | nazwa | miasto |
---|---|---|
123 | Michael Jackson | Gary, Indiana |
456 | John Lennon | Liverpool, Anglia |
789 | Dolly Parton | Hrabstwo Sevier, Tennessee |
Ponieważ nie musisz przechowywać kolumny rowid
, zapytania id
są szybkie. Pamiętaj, że tabela jest teraz sortowana według wartości id
, a nie według kolejności wstawiania.
Przyspieszanie zapytań za pomocą indeksów
SQLite używa indektów do przyspieszania zapytań. Jeśli podczas filtrowania (WHERE
), sortowania (ORDER BY
) lub agregowania (GROUP BY
) kolumny tabela ma dla niej indeks, zapytanie jest przyspieszone.
W poprzednim przykładzie filtrowanie według kolumny city
wymaga skanowania całej tabeli:
SELECT id, name
WHERE city = 'London, England';
W przypadku aplikacji, która zawiera wiele 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 przypisana do rowid
:
miasto | rowid |
---|---|
Gary, Indiana | 2 |
Liverpool, Anglia | 1 |
Hrabstwo Sevier, Tennessee | 3 |
Pamiętaj, że koszt miejsca na dane w kolumnie city
jest teraz podwójny, ponieważ występuje zarówno w pierwotnej tabeli, jak i w indeksie. Korzystasz z indeksu, więc koszt dodatkowego miejsca na dane jest wart korzyści płynących z szybszego wykonywania zapytań.
Nie utrzymuj jednak indeksu, którego nie używasz, aby uniknąć płacenia za miejsce na dane, które nie przynosi korzyści w zakresie wydajności zapytań.
Tworzenie indeksów wielokolumnowych
Jeśli zapytania łączą wiele kolumn, możesz utworzyć indeksy wielokolumnowe, aby w pełni przyspieszyć zapytania. Możesz też użyć indeksu w kolumnie zewnętrznej i zezwolić, aby wewnętrzne wyszukiwanie było wykonywane jako skanowanie liniowe.
Na przykład w przypadku tego zapytania:
SELECT id, name
WHERE city = 'London, England'
ORDER BY city, name
Możesz przyspieszyć zapytanie, korzystając z indeksu wielokolumnowego w takim samym porządku, jak podano w zapytaniu:
CREATE INDEX city_name_index ON Customers(city, name);
Jeśli jednak masz tylko indeks na city
, sortowanie zewnętrzne jest nadal przyspieszone, a sortowanie wewnętrzne wymaga skanowania liniowego.
Ta funkcja działa też w przypadku zapytań z prefiksem. Na przykład indeksON 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 danym porządku.
Rozważ WITHOUT ROWID
Domyślnie SQLite tworzy dla tabeli kolumnę rowid
, gdzie rowid
jest domyślną INTEGER PRIMARY KEY AUTOINCREMENT
. Jeśli masz już kolumnę o nazwie INTEGER PRIMARY KEY
, ta kolumna stanie się aliasem kolumny rowid
.
W przypadku tabel, które mają klucz podstawowy inny niż INTEGER
lub złożony z kolumn, rozważ użycie WITHOUT
ROWID
.
przechowywać małe dane jako BLOB
, a duże dane jako plik;
Jeśli chcesz powiązać z wierszem duże dane, np. miniaturę obrazu lub zdjęcie kontaktu, możesz je zapisać w kolumnie BLOB
lub w pliku, a potem w kolumnie zapisać ścieżkę do pliku.
Pliki są zazwyczaj zaokrąglone do 4 KB. W przypadku bardzo małych plików, w których przypadku błąd zaokrąglenia jest znaczący, wydajniejsze jest przechowywanie ich w bazie danych jako BLOB
. SQLite minimalizuje wywołania systemu plików i w niektórych przypadkach jest szybsze niż system plików.
Poprawianie wydajności zapytań
Aby zwiększyć wydajność zapytań w SQLite przez minimalizowanie czasu odpowiedzi i maksymalizowanie efektywności przetwarzania, stosuj te sprawdzone metody.
odczytywać tylko wiersze, których potrzebujesz;
Filtry umożliwiają zawężenie wyników przez określenie określonych kryteriów, takich jak zakres dat, lokalizacja lub nazwa. Limity umożliwiają kontrolowanie liczby wyświetlanych wyników:
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()) { ... } }
odczytywać tylko te kolumny, których potrzebujesz;
Unikaj wybierania niepotrzebnych kolumn, które mogą spowolnić zapytania i marnować zasoby. Zamiast tego wybierz tylko kolumny, które są używane.
W tym przykładzie wybierasz id
, name
i phone
:
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); ... } }
Wystarczy jednak kolumna 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); ... } }
Parametryzuj zapytania za pomocą kart SQL, a nie przez konkatenację ciągu znaków.
Ciąg zapytania może zawierać parametr, który jest znany dopiero w czasie wykonywania, np.:
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; } } }
W powyższym kodzie każde zapytanie tworzy inny ciąg znaków, więc nie można korzystać z pamięci podręcznej instrukcji. Każde wywołanie wymaga skompilowania kodu SQLite, zanim będzie można je wykonać. Zamiast tego możesz zastąpić argument id
parametrem i powiązać wartość z parametrem 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; } } }
Teraz zapytanie można skompilować raz i zapisać w pamięci podręcznej. Kompilowane zapytanie jest używane ponownie w różnych wywołaniach funkcji getNameById(long)
.
Iteruj w kodzie SQL, a nie w kodzie programu
Zamiast pętli programowej iterującej zapytania SQL w celu zwrócenia poszczególnych wyników, użyj pojedynczego zapytania zwracającego wszystkie wyszukiwane wyniki. Automatyczny cykl jest około 1000 razy wolniejszy niż pojedyncze zapytanie SQL.
Użyj kolumny DISTINCT
do przechowywania unikalnych wartości
Użycie słowa kluczowego DISTINCT
może poprawić 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 funkcji 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 ... } }
Stosuj funkcje agregujące, gdy tylko jest to możliwe
Używaj funkcji agregacji do wyników zbiorczych bez danych wierszy. Na przykład kod poniżej sprawdza, czy istnieje co najmniej 1 wiersz pasujący do kryteriów:
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 ... } }
Aby pobrać tylko pierwszy wiersz, możesz użyć funkcji EXISTS()
, która zwróci wartość 0
, jeśli pasujący wiersz nie istnieje, i wartość 1
, jeśli pasuje co najmniej 1 wiersz:
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 ... } }
Używaj w kodzie aplikacji funkcji agregacji SQLite:
COUNT
: zlicza, ile wierszy jest w kolumnie.SUM
: dodaje wszystkie wartości liczbowe w kolumnie.MIN
lubMAX
: określają one najmniejszą lub najwyższą wartość. Działa w przypadku kolumn liczbowych, typówDATE
i typów tekstowych.AVG
: zwraca średnią wartość liczbową.GROUP_CONCAT
: łączenie ciągów znaków za pomocą opcjonalnego separatora.
Użyj COUNT()
zamiast Cursor.getCount()
.
W tym przykładzie funkcja Cursor.getCount()
odczytuje wszystkie wiersze z bazy danych i zwróci wszystkie ich wartości:
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(); ... }
Jednak przy użyciu funkcji COUNT()
baza danych zwraca tylko liczbę:
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); ... }
zapytania do Nest zamiast kodu,
SQL jest kompozytywny i obsługuje zapytania w nawiasach klamrowych, złączenia i ograniczenia kluczy obcych. Wyniki jednego zapytania możesz wykorzystać w innym bez konieczności korzystania z aplikacji. Dzięki temu nie trzeba kopiować danych z SQLite, a silnik bazy danych może zoptymalizować zapytanie.
W tym przykładzie możesz uruchomić zapytanie, aby znaleźć miasto z największą liczbą klientów, a potem użyć tego wyniku w innym zapytaniu, aby znaleźć wszystkich klientów z tego miasta:
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()) { ... } } } }
Aby uzyskać wynik w połowie czasu w poprzednim przykładzie, użyj pojedynczego zapytania SQL z zagnieszkami:
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()) { ... } }
Sprawdzanie unikalności w SQL
Jeśli wiersz nie może zostać wstawiony, dopóki dana wartość kolumny nie jest unikalna w tabeli, może być bardziej efektywne wymuszenie tej unikalności jako ograniczenia kolumny.
W tym przykładzie jedno zapytanie służy do sprawdzenia wiersza, który ma zostać wstawiony, a drugie – do jego faktycznego wstawienia:
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, });
Zamiast sprawdzać ograniczenie unikalne w Kotlinie lub Javie, możesz to zrobić w SQL podczas definiowania tabeli:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
username TEXT UNIQUE
);
SQLite robi to samo:
CREATE TABLE Customers(...);
CREATE UNIQUE INDEX CustomersUsername ON Customers(username);
Teraz możesz wstawić wiersz i pozwolić SQLite na sprawdzenie ograniczenia:
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 obsługuje unikalne indeksy z wieloma kolumnami:
CREATE TABLE table(...);
CREATE UNIQUE INDEX unique_table ON table(column1, column2, ...);
SQLite weryfikuje ograniczenia szybciej i z mniejszym obciążeniem niż kod Kotlin lub Java. Sprawdzoną metodą jest używanie SQLite zamiast kodu aplikacji.
Przeprowadzanie wielu wstawień w ramach jednej transakcji
Transakcja powoduje wykonanie wielu operacji, co poprawia nie tylko wydajność, ale też poprawność. Aby zwiększyć spójność danych i przyspieszyć skuteczność, możesz grupować w paczkach:
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() }
Korzystanie z narzędzi do rozwiązywania problemów
SQLite udostępnia te narzędzia do rozwiązywania problemów, które ułatwiają pomiar wydajności.
Używanie interaktywnego promptu SQLite
Uruchom SQLite na swoim komputerze, aby wykonywać zapytania i uczyć się.
Różne wersje platformy Android używają różnych wersji SQLite. Aby użyć tego samego silnika, którego używa się na urządzeniu z Androidem, użyj adb shell
i uruchom sqlite3
na urządzeniu docelowym.
Możesz poprosić SQLite o zmierzenie czasu wykonywania 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
W poprzednim przykładzie znalezienie wszystkich klientów z Paryża wymaga pełnego skanowania tabeli bez indeksu. Jest to tzw. złożoność liniowa. SQLite musi odczytać wszystkie wiersze i zatrzymać 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 korzystasz z interaktywnej powłoki, możesz poprosić SQLite, aby zawsze wyświetlało plany zapytań:
sqlite> .eqp on
Więcej informacji znajdziesz w artykule Planowanie zapytań.
SQLite Analyzer
SQLite udostępnia interfejs wiersza poleceń sqlite3_analyzer
, który umożliwia zapisywanie dodatkowych informacji, które można wykorzystać do rozwiązywania problemów z wydajnością. Aby zainstalować SQLite, otwórz stronę pobierania SQLite.
Za pomocą polecenia adb pull
możesz pobrać plik bazy danych z urządzenia docelowego na stację roboczą w celu przeanalizowania:
adb pull /data/data/<app_package_name>/databases/<db_name>.db
SQLite Browser
Możesz też zainstalować narzędzie GUI SQLite Browser na stronie Pobieranie SQLite.
Logowanie w Androidzie
Android rejestruje czas wykonywania zapytań SQLite i je loguje:
# 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 usługi Peretto 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"
}
}
}
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy obsługa JavaScript jest wyłączona
- Przeprowadzanie testów porównawczych w ramach ciągłej integracji
- Zablokowane klatki
- Tworzenie i analizowanie profili bazowych bez korzystania z Macrobenchmark