Android offre il supporto integrato per SQLite, un database SQL efficiente. Segui queste best practice per ottimizzare il rendimento della tua app, assicurandoti che rimanga veloce e prevedibilmente veloce man mano che i dati aumentano. Se utilizzi queste best practice, riduci anche la possibilità di riscontrare problemi di rendimento difficili da riprodurre e risolvere.
Per ottenere prestazioni più rapide, segui questi principi:
Leggi meno righe e colonne: ottimizza le query per recuperare solo i dati necessari. Ridurre al minimo la quantità di dati letti dal database, perché il recupero di dati in eccesso può influire sulle prestazioni.
Trasferisci il lavoro al motore SQLite: esegui calcoli, filtri e ordinamenti all'interno delle query SQL. L'utilizzo del motore di query di SQLite può migliorare notevolmente le prestazioni.
Modifica lo schema del database: progetta lo schema del database per aiutare SQLite a creare piani di query e rappresentazioni dei dati efficienti. Indicizza correttamente le tabelle e ottimizza le strutture delle tabelle per migliorare le prestazioni.
Inoltre, puoi utilizzare gli strumenti di risoluzione dei problemi disponibili per misurare il rendimento del tuo database SQLite e identificare le aree che richiedono ottimizzazione.
Ti consigliamo di utilizzare la libreria Jetpack Room.
Configura il database per le prestazioni
Segui i passaggi descritti in questa sezione per configurare il database in modo da ottenere prestazioni ottimali in SQLite.
Abilita la registrazione write-ahead
SQLite implementa le mutazioni aggiungendole a un log, che di tanto in tanto compatta nel database. Questa procedura è chiamata Write-Ahead Logging (WAL).
Attiva
WAL
a meno che tu non stia utilizzando ATTACH
DATABASE
.
Rilassare la modalità di sincronizzazione
Quando utilizzi WAL, per impostazione predefinita ogni commit genera un fsync
per garantire che
i dati raggiungano il disco. Ciò migliora la durabilità dei dati, ma rallenta i commit.
SQLite offre un'opzione per controllare la modalità
sincrona. Se
attivi WAL, imposta la modalità sincrona su NORMAL
:
Kotlin
db.execSQL("PRAGMA synchronous = NORMAL")
Java
db.execSQL("PRAGMA synchronous = NORMAL");
In questa impostazione, un commit può essere restituito prima che i dati vengano archiviati su un disco. Se si verifica l'arresto di un dispositivo, ad esempio in caso di interruzione dell'alimentazione o di un kernel panic, i dati di cui è stato eseguito il commit potrebbero andare persi. Tuttavia, a causa della registrazione, il database non è danneggiato.
Se si arresta solo l'app, i dati raggiungono comunque il disco. Per la maggior parte delle app, questa impostazione migliora il rendimento senza costi significativi.
Definisci schemi di tabelle efficienti
Per ottimizzare il rendimento e ridurre al minimo il consumo di dati, definisci uno schema di tabella efficiente. SQLite crea piani di query e dati efficienti, il che porta a un recupero dei dati più rapido. Questa sezione fornisce le best practice per la creazione di schemi di tabelle.
Prendi in considerazione INTEGER PRIMARY KEY
Per questo esempio, definisci e compila una tabella come segue:
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');
L'output della tabella è il seguente:
rowid | id | nome | città |
---|---|---|---|
1 | 456 | John Lennon | Liverpool, Inghilterra |
2 | 123 | Michael Jackson | Gary, IN |
3 | 789 | Dolly Parton | Contea di Sevier, TN |
La colonna rowid
è
un indice che conserva l'ordine di inserimento. Le query che
filtrano in base a rowid
vengono implementate come una rapida ricerca B-tree, mentre le query che
filtrano in base a id
sono una lenta scansione della tabella.
Se prevedi di eseguire ricerche in base a id
, puoi evitare di archiviare la
colonna rowid
per ridurre i dati archiviati e ottenere un database
più veloce:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
city TEXT
);
La tabella ora ha il seguente aspetto:
id | nome | città |
---|---|---|
123 | Michael Jackson | Gary, IN |
456 | John Lennon | Liverpool, Inghilterra |
789 | Dolly Parton | Contea di Sevier, TN |
Poiché non è necessario archiviare la colonna rowid
, le query id
sono veloci. Tieni presente
che la tabella ora è ordinata in base a id
anziché all'ordine di inserzione.
Accelerare le query con gli indici
SQLite utilizza
indici
per accelerare le query. Quando filtri (WHERE
), ordini (ORDER BY
) o
aggregi (GROUP BY
) una colonna, se la tabella ha un indice per la colonna, la
query viene accelerata.
Nell'esempio precedente, il filtro per city
richiede la scansione dell'intera tabella:
SELECT id, name
WHERE city = 'London, England';
Per un'app con molte query sulle città, puoi accelerare queste query con un indice:
CREATE INDEX city_index ON Customers(city);
Un indice viene implementato come tabella aggiuntiva, ordinata in base alla colonna dell'indice e
mappata a rowid
:
città | rowid |
---|---|
Gary, IN | 2 |
Liverpool, Inghilterra | 1 |
Contea di Sevier, TN | 3 |
Tieni presente che il costo di archiviazione della colonna city
è ora doppio, perché
è presente sia nella tabella originale che nell'indice. Poiché utilizzi l'indice, il costo dello spazio di archiviazione aggiunto vale il vantaggio di query più veloci.
Tuttavia, non mantenere un indice che non utilizzi per evitare di pagare il costo di archiviazione senza ottenere alcun miglioramento delle prestazioni delle query.
Crea indici a più colonne
Se le query combinano più colonne, puoi creare indici multicolonna per accelerare completamente la query. Puoi anche utilizzare un indice su una colonna esterna e lasciare che la ricerca interna venga eseguita come scansione lineare.
Ad esempio, data la seguente query:
SELECT id, name
WHERE city = 'London, England'
ORDER BY city, name
Puoi accelerare la query con un indice multicolonna nello stesso ordine specificato nella query:
CREATE INDEX city_name_index ON Customers(city, name);
Tuttavia, se hai solo un indice su city
, l'ordinamento esterno è comunque
accelerato, mentre l'ordinamento interno richiede una scansione lineare.
Funziona anche con le richieste di prefisso. Ad esempio, un indice
ON Customers (city, name)
accelera anche il filtraggio, l'ordinamento e il raggruppamento
per city
, poiché la tabella degli indici per un indice multicolonna è ordinata in base agli
indici specificati nell'ordine indicato.
Prendi in considerazione WITHOUT ROWID
Per impostazione predefinita, SQLite crea una colonna rowid
per la tabella, dove rowid
è un
INTEGER PRIMARY KEY AUTOINCREMENT
implicito. Se hai già una colonna che
è INTEGER PRIMARY KEY
, questa colonna diventa un alias di rowid
.
Per le tabelle che hanno una chiave primaria diversa da INTEGER
o una combinazione di colonne, valuta l'utilizzo di WITHOUT
ROWID
.
Archivia i dati di piccole dimensioni come BLOB
e quelli di grandi dimensioni come file
Se vuoi associare dati di grandi dimensioni a una riga, ad esempio la miniatura di un'immagine
o una foto per un contatto, puoi archiviarli in una colonna BLOB
o in
un file, quindi archiviare il percorso del file nella colonna.
In genere, i file vengono arrotondati a incrementi di 4 KB. Per i file molto piccoli, in cui
l'errore di arrotondamento è significativo, è più efficiente archiviarli nel
database come BLOB
. SQLite riduce al minimo le chiamate al file system ed è più veloce del
file system sottostante
in alcuni casi.
Migliorare le prestazioni delle query
Segui queste best practice per migliorare le prestazioni delle query in SQLite riducendo al minimo i tempi di risposta e massimizzando l'efficienza di elaborazione.
Leggere solo le righe necessarie
I filtri ti consentono di restringere i risultati specificando determinati criteri, come intervallo di date, posizione o nome. I limiti ti consentono di controllare il numero di risultati visualizzati:
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()) { ... } }
Leggere solo le colonne necessarie
Evita di selezionare colonne non necessarie, che possono rallentare le query e sprecare risorse. Seleziona invece solo le colonne utilizzate.
Nell'esempio seguente, seleziona id
, name
e 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); ... } }
Tuttavia, è necessaria solo la colonna 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); ... } }
Parametrizzare le query
La stringa di query potrebbe includere un parametro noto solo in fase di runtime, ad esempio:
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; } } }
Nel codice precedente, ogni query crea una stringa diversa e quindi
non trae vantaggio dalla cache delle istruzioni. Ogni chiamata richiede la compilazione di SQLite
prima di poter essere eseguita. Puoi invece sostituire l'argomento id
con un
parametro e
associare il valore a 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; } } }
Ora la query può essere compilata una sola volta e memorizzata nella cache. La query compilata viene riutilizzata
tra diverse invocazioni di getNameById(long)
.
Esegui l'iterazione in SQL, non nel codice
Utilizza una singola query che restituisce tutti i risultati mirati, anziché un ciclo programmatico che scorre le query SQL per restituire i singoli risultati. Il ciclo programmatico è circa 1000 volte più lento di una singola query SQL.
Utilizza DISTINCT
per i valori univoci
L'utilizzo della parola chiave DISTINCT
può migliorare il rendimento delle query riducendo la quantità di dati da elaborare. Ad esempio, se vuoi
restituire solo i valori univoci di una colonna, utilizza 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 ... } }
Utilizza le funzioni di aggregazione quando possibile
Utilizza le funzioni di aggregazione per ottenere risultati aggregati senza dati di riga. Ad esempio, il seguente codice verifica se esiste almeno una riga corrispondente:
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 ... } }
Per recuperare solo la prima riga, puoi utilizzare EXISTS()
per restituire 0
se non esiste una riga corrispondente e 1
se una o più righe corrispondono:
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 ... } }
Utilizza le funzioni di aggregazione SQLite nel codice dell'app:
COUNT
: conta il numero di righe in una colonna.SUM
: somma tutti i valori numerici di una colonna.MIN
oMAX
: determina il valore più basso o più alto. Funziona per le colonne numeriche, i tipiDATE
e i tipi di testo.AVG
: trova il valore numerico medio.GROUP_CONCAT
: concatena le stringhe con un separatore facoltativo.
Utilizza COUNT()
al posto di Cursor.getCount()
Nell'esempio
seguente, la
funzione Cursor.getCount()
legge tutte le righe del database e restituisce tutti i valori delle righe:
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(); ... }
Tuttavia, utilizzando COUNT()
, il database restituisce solo il conteggio:
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); ... }
Query Nest anziché codice
SQL è componibile e supporta sottoquery, join e vincoli di chiave esterna. Puoi utilizzare il risultato di una query in un'altra query senza passare per il codice dell'app. In questo modo si riduce la necessità di copiare i dati da SQLite e il motore del database può ottimizzare la query.
Nell'esempio seguente, puoi eseguire una query per trovare la città con il maggior numero di clienti, quindi utilizzare il risultato in un'altra query per trovare tutti i clienti di quella città:
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()) { ... } } } }
Per ottenere il risultato nella metà del tempo dell'esempio precedente, utilizza una singola query SQL con istruzioni nidificate:
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()) { ... } }
Controllare l'unicità in SQL
Se una riga non deve essere inserita a meno che un determinato valore di colonna non sia univoco nella tabella, potrebbe essere più efficiente applicare questa univocità come vincolo di colonna.
Nell'esempio seguente, viene eseguita una query per convalidare la riga da inserire e un'altra per l'inserimento vero e proprio:
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, });
Anziché controllare il vincolo univoco in Kotlin o Java, puoi controllarlo in SQL quando definisci la tabella:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
username TEXT UNIQUE
);
SQLite esegue la stessa operazione di quanto segue:
CREATE TABLE Customers(...);
CREATE UNIQUE INDEX CustomersUsername ON Customers(username);
Ora puoi inserire una riga e lasciare che SQLite controlli il vincolo:
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 supporta indici univoci con più colonne:
CREATE TABLE table(...);
CREATE UNIQUE INDEX unique_table ON table(column1, column2, ...);
SQLite convalida i vincoli più rapidamente e con meno overhead rispetto al codice Kotlin o Java. È una best practice utilizzare SQLite anziché il codice dell'app.
Raggruppa più inserimenti in un'unica transazione
Una transazione esegue più operazioni, il che migliora non solo l'efficienza, ma anche la correttezza. Per migliorare la coerenza dei dati e accelerare le prestazioni, puoi raggruppare gli inserimenti:
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() }
Utilizzare strumenti per la risoluzione dei problemi
SQLite fornisce i seguenti strumenti di risoluzione dei problemi per misurare il rendimento.
Utilizzare il prompt interattivo di SQLite
Esegui SQLite sul tuo computer per eseguire query e imparare.
Versioni diverse della piattaforma Android utilizzano revisioni diverse di SQLite. Per utilizzare
lo stesso motore presente su un dispositivo Android, utilizza adb shell
ed
esegui sqlite3
sul dispositivo di destinazione.
Puoi chiedere a SQLite di cronometrare le query:
sqlite> .timer on
sqlite> SELECT ...
Run Time: real ... user ... sys ...
EXPLAIN QUERY PLAN
Puoi chiedere a SQLite di spiegare come intende rispondere a una query utilizzando
EXPLAIN QUERY PLAN
:
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SCAN Customers
L'esempio precedente richiede una scansione completa della tabella senza un indice per trovare tutti i clienti di Parigi. Questo tipo di complessità è chiamato complessità lineare. SQLite deve leggere tutte le righe e conservare solo quelle che corrispondono ai clienti di Parigi. Per risolvere il problema, puoi aggiungere un indice:
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=?
Se utilizzi la shell interattiva, puoi chiedere a SQLite di spiegare sempre i piani di query:
sqlite> .eqp on
Per maggiori informazioni, consulta la sezione Pianificazione delle query.
Strumento di analisi SQLite
SQLite offre l'interfaccia a riga di comando (CLI) sqlite3_analyzer
per scaricare ulteriori informazioni che possono essere utilizzate per risolvere i problemi di prestazioni. Per l'installazione, visita la
pagina di download di SQLite.
Puoi utilizzare adb pull
per scaricare un file di database da un dispositivo di destinazione sulla tua
workstation per l'analisi:
adb pull /data/data/<app_package_name>/databases/<db_name>.db
SQLite Browser
Puoi anche installare lo strumento GUI SQLite Browser nella pagina dei download di SQLite.
Logging Android
Android registra le query SQLite e le registra per te:
# Enable query time logging
$ adb shell setprop log.tag.SQLiteTime VERBOSE
# Disable query time logging
$ adb shell setprop log.tag.SQLiteTime ERROR
Perfetto tracing
Quando configuri Perfetto, puoi aggiungere quanto segue per includere le tracce per le singole query:
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
atrace_categories: "database"
}
}
}
dumpsys meminfo
adb shell dumpsys meminfo <package-name>
stamperà statistiche relative all'utilizzo della memoria dell'app, inclusi alcuni dettagli sulla memoria SQLite. Ad esempio, questo è stato
estratto dall'output di
adb shell dumpsys meminfo com.google.android.gms.persistent
sul dispositivo
di uno sviluppatore:
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
In DATABASES
troverai:
pgsz
: le dimensioni di una pagina del database in KB.dbsz
: le dimensioni dell'intero database, in pagine. Per ottenere le dimensioni in KB, moltiplicapgsz
perdbsz
.Lookaside(b)
: memoria allocata al buffer lookaside SQLite per connessione, in byte. In genere sono molto piccoli.cache hits
: SQLite gestisce una cache delle pagine del database. Questo è il numero di hit della cache della pagina (conteggio).cache misses
: numero di mancati riscontri della cache di pagine (conteggio).cache size
: numero di pagine nella cache (conteggio). Per ottenere le dimensioni in KB, moltiplica questo numero perpgsz
.Dbname
: percorso del file DB. Nel nostro esempio, ad alcuni database è stato aggiunto(1)
o un altro numero al nome per indicare che esiste più di una connessione allo stesso database sottostante. Le statistiche vengono monitorate per connessione.
In POOL STATS
troverai:
cache hits
: SQLite memorizza nella cache le istruzioni preparate e tenta di riutilizzarle quando esegue query, per risparmiare un po' di lavoro e memoria nella compilazione delle istruzioni SQL. Il numero di hit della cache delle istruzioni (conteggio).cache misses
: numero di mancati riscontri della cache delle istruzioni (conteggio).cache size
: a partire da Android 17, questo valore elenca il numero totale di istruzioni preparate nella cache. Nelle versioni precedenti, questo valore equivale alla somma di hit e mancati riscontri elencati nelle altre due colonne e non rappresenta la dimensione della cache.
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Eseguire benchmark nell'integrazione continua
- Frame bloccati
- Creare e misurare profili di base senza Macrobenchmark