Android предлагает встроенную поддержку SQLite , эффективной базы данных SQL. Следуйте этим рекомендациям, чтобы оптимизировать производительность вашего приложения, обеспечивая его высокую и предсказуемую скорость по мере роста объёма данных. Используя эти рекомендации, вы также снижаете вероятность возникновения проблем с производительностью, которые сложно воспроизвести и устранить.
Чтобы добиться более высокой производительности, следуйте следующим принципам производительности:
Уменьшите количество считываемых строк и столбцов : оптимизируйте запросы, чтобы извлекать только необходимые данные. Минимизируйте объём считываемых из базы данных данных, поскольку избыточное извлечение данных может повлиять на производительность.
Передача задач движку SQLite : выполнение вычислений, фильтрации и сортировки в SQL-запросах. Использование движка запросов SQLite может значительно повысить производительность.
Измените схему базы данных : разработайте схему базы данных, чтобы SQLite могла эффективно создавать планы запросов и представления данных. Корректно индексируйте таблицы и оптимизируйте их структуру для повышения производительности.
Кроме того, вы можете использовать доступные инструменты устранения неполадок для измерения производительности вашей базы данных SQLite, чтобы выявить области, требующие оптимизации.
Мы рекомендуем использовать библиотеку Jetpack Room .
Настройте базу данных для повышения производительности
Следуйте инструкциям в этом разделе, чтобы настроить базу данных для оптимальной производительности в SQLite.
Включить опережающую запись журнала
SQLite реализует мутации, добавляя их в журнал, который периодически сжимается в базу данных. Это называется опережающей записью (WAL) .
Включите WAL, если вы не используете ATTACH DATABASE
.
Ослабьте режим синхронизации
При использовании WAL по умолчанию каждый коммит запускает fsync
, чтобы гарантировать, что данные будут записаны на диск. Это повышает сохранность данных, но замедляет коммиты.
В SQLite есть возможность управления синхронным режимом . Если вы включите WAL, установите синхронный режим на NORMAL
:
Котлин
db.execSQL("PRAGMA synchronous = NORMAL")
Ява
db.execSQL("PRAGMA synchronous = NORMAL");
В этом случае фиксация может быть завершена до того, как данные будут сохранены на диске. При отключении устройства, например, из-за отключения питания или паники ядра, зафиксированные данные могут быть потеряны. Однако благодаря журналированию ваша база данных не повреждается.
Если сбой произойдёт только в вашем приложении, ваши данные всё равно попадут на диск. Для большинства приложений эта настройка обеспечивает повышение производительности без существенных затрат.
Определить эффективные схемы таблиц
Чтобы оптимизировать производительность и минимизировать потребление данных, определите эффективную схему таблицы. SQLite формирует эффективные планы запросов и данные, что ускоряет их извлечение. В этом разделе представлены рекомендации по созданию схем таблиц.
Рассмотрим INTEGER PRIMARY KEY
Для этого примера определите и заполните таблицу следующим образом:
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');
Вывод таблицы выглядит следующим образом:
роувид | идентификатор | имя | город |
---|---|---|---|
1 | 456 | Джон Леннон | Ливерпуль, Англия |
2 | 123 | Майкл Джексон | Гэри, Индиана |
3 | 789 | Долли Партон | Округ Севиер, штат Теннесси |
Столбец rowid
представляет собой индекс, сохраняющий порядок вставки. Запросы с фильтрацией по rowid
реализуются как быстрый поиск по B-дереву, но запросы с фильтрацией по id
представляют собой медленное сканирование таблицы.
Если вы планируете выполнять поиск по id
, вы можете не хранить столбец rowid
, что позволит сэкономить данные и повысить общую скорость работы базы данных:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
city TEXT
);
Теперь ваша таблица выглядит следующим образом:
идентификатор | имя | город |
---|---|---|
123 | Майкл Джексон | Гэри, Индиана |
456 | Джон Леннон | Ливерпуль, Англия |
789 | Долли Партон | Округ Севиер, штат Теннесси |
Поскольку хранить столбец rowid
не нужно, запросы id
выполняются быстро. Обратите внимание, что таблица теперь сортируется по id
а не по порядку вставки.
Ускорение запросов с помощью индексов
SQLite использует индексы для ускорения запросов. При фильтрации ( WHERE
), сортировке ( ORDER BY
) или агрегации ( GROUP BY
) столбца, если в таблице есть индекс для этого столбца, запрос ускоряется.
В предыдущем примере фильтрация по city
требовала сканирования всей таблицы:
SELECT id, name
WHERE city = 'London, England';
Для приложения с большим количеством запросов о городах вы можете ускорить эти запросы с помощью индекса:
CREATE INDEX city_index ON Customers(city);
Индекс реализован как дополнительная таблица, отсортированная по столбцу индекса и сопоставленная с rowid
:
город | роувид |
---|---|
Гэри, Индиана | 2 |
Ливерпуль, Англия | 1 |
Округ Севиер, штат Теннесси | 3 |
Обратите внимание, что стоимость хранения столбца city
теперь удвоена, поскольку он присутствует как в исходной таблице, так и в индексе. Поскольку вы используете индекс, стоимость дополнительного хранилища оправдывает преимущества более быстрых запросов. Однако не поддерживайте индекс, который вы не используете, чтобы избежать расходов на хранилище без прироста производительности запросов.
Создание многостолбцовых индексов
Если ваши запросы объединяют несколько столбцов, вы можете создать многостолбцовые индексы для максимального ускорения выполнения запроса. Также можно использовать индекс по внешнему столбцу и выполнить внутренний поиск как линейное сканирование.
Например, если задан следующий запрос:
SELECT id, name
WHERE city = 'London, England'
ORDER BY city, name
Вы можете ускорить запрос с помощью многостолбцового индекса в том же порядке, который указан в запросе:
CREATE INDEX city_name_index ON Customers(city, name);
Однако если у вас есть только индекс по city
, внешнее упорядочивание все равно ускоряется, тогда как внутреннее упорядочивание требует линейного сканирования.
Это также работает с префиксными запросами. Например, индекс ON Customers (city, name)
также ускоряет фильтрацию, упорядочивание и группировку по city
, поскольку таблица индекса для многостолбцового индекса упорядочена по заданным индексам в заданном порядке.
Рассмотреть WITHOUT ROWID
По умолчанию SQLite создаёт для вашей таблицы столбец rowid
, где rowid
— это неявный INTEGER PRIMARY KEY AUTOINCREMENT
. Если у вас уже есть столбец с INTEGER PRIMARY KEY
, этот столбец станет псевдонимом rowid
.
Для таблиц, имеющих первичный ключ, отличный от INTEGER
или состоящий из нескольких столбцов, рассмотрите вариант WITHOUT ROWID
.
Храните небольшие данные в виде BLOB
, а большие данные — в виде файлов.
Если вы хотите связать большой объем данных со строкой, например, миниатюру изображения или фотографию контакта, вы можете сохранить данные либо в столбце BLOB
, либо в файле, а затем сохранить путь к файлу в столбце.
Файлы обычно округляются до 4 КБ. Для очень маленьких файлов, где погрешность округления значительна, эффективнее хранить их в базе данных как BLOB
. SQLite минимизирует вызовы файловой системы и в некоторых случаях работает быстрее, чем базовая файловая система .
Улучшить производительность запросов
Следуйте этим рекомендациям, чтобы повысить производительность запросов в SQLite, минимизируя время отклика и максимизируя эффективность обработки.
Читайте только те строки, которые вам нужны
Фильтры позволяют сузить результаты поиска, указав определённые критерии, такие как диапазон дат, местоположение или имя. Ограничения позволяют контролировать количество отображаемых результатов:
Котлин
db.rawQuery(""" SELECT name FROM Customers LIMIT 10; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { ... } }
Ява
try (Cursor cursor = db.rawQuery(""" SELECT name FROM Customers LIMIT 10; """, null)) { while (cursor.moveToNext()) { ... } }
Читайте только те столбцы, которые вам нужны
Избегайте выбора ненужных столбцов, так как это может замедлить выполнение запросов и привести к ненужной трате ресурсов. Вместо этого выбирайте только те столбцы, которые используются.
В следующем примере вы выбираете id
, name
и 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) // ... } }
Ява
// 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); ... } }
Однако вам понадобится только столбец name
:
Котлин
db.rawQuery(""" SELECT name FROM Customers; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { val name = cursor.getString(0) ... } }
Ява
try (Cursor cursor = db.rawQuery(""" SELECT name FROM Customers; """, null)) { while (cursor.moveToNext()) { String name = cursor.getString(0); ... } }
Параметризация запросов
Строка запроса может включать параметр, который известен только во время выполнения, например следующий:
Котлин
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 } } }
Ява
@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; } } }
В приведенном выше коде каждый запрос создаёт отдельную строку и, следовательно, не использует кэш операторов. Для выполнения каждого вызова требуется компиляция SQLite. Вместо этого можно заменить аргумент id
параметром и привязать значение к 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 } } }
Ява
@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; } } }
Теперь запрос можно скомпилировать один раз и кэшировать. Скомпилированный запрос можно использовать повторно между различными вызовами getNameById(long)
.
Итерация в SQL, а не в коде
Используйте один запрос, возвращающий все целевые результаты, вместо программного цикла, итерирующего SQL-запросы для возврата отдельных результатов. Программный цикл примерно в 1000 раз медленнее, чем один SQL-запрос.
Используйте DISTINCT
для уникальных значений.
Использование ключевого слова DISTINCT
может повысить производительность запросов за счёт сокращения объёма обрабатываемых данных. Например, если вы хотите вернуть только уникальные значения из столбца, используйте DISTINCT
:
Котлин
db.rawQuery(""" SELECT DISTINCT name FROM Customers; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { // Only iterate over distinct names in Kotlin ... } }
Ява
try (Cursor cursor = db.rawQuery(""" SELECT DISTINCT name FROM Customers; """, null)) { while (cursor.moveToNext()) { // Only iterate over distinct names in Java ... } }
Используйте агрегатные функции везде, где это возможно
Используйте агрегатные функции для агрегирования результатов без данных строк. Например, следующий код проверяет наличие хотя бы одной совпадающей строки:
Котлин
// 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 ... }
Ява
// 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 ... } }
Чтобы извлечь только первую строку, можно использовать EXISTS()
чтобы вернуть 0
если соответствующей строки не существует, и 1
если совпадают одна или несколько строк:
Котлин
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 ... } }
Ява
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 ... } }
Используйте агрегатные функции SQLite в коде вашего приложения:
-
COUNT
: подсчитывает количество строк в столбце. -
SUM
: складывает все числовые значения в столбце. -
MIN
илиMAX
: определяет наименьшее или наибольшее значение. Подходит для числовых столбцов, столбцов типаDATE
и текстовых столбцов. -
AVG
: находит среднее числовое значение. -
GROUP_CONCAT
: объединяет строки с необязательным разделителем.
Используйте COUNT()
вместо Cursor.getCount()
В следующем примере функция Cursor.getCount()
считывает все строки из базы данных и возвращает все значения строк:
Котлин
// 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() }
Ява
// 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(); ... }
Однако при использовании COUNT()
база данных возвращает только количество:
Котлин
db.rawQuery(""" SELECT COUNT(*) FROM Customers; """.trimIndent(), null ).use { cursor -> cursor.moveToFirst() val count = cursor.getInt(0) }
Ява
try (Cursor cursor = db.rawQuery(""" SELECT COUNT(*) FROM Customers; """, null)) { cursor.moveToFirst(); int count = cursor.getInt(0); ... }
Вложенные запросы вместо кода
SQL поддерживает компоновку и подзапросы, соединения и ограничения внешнего ключа. Вы можете использовать результат одного запроса в другом запросе, не выполняя код приложения. Это уменьшает необходимость копирования данных из SQLite и позволяет СУБД оптимизировать ваш запрос.
В следующем примере вы можете выполнить запрос, чтобы определить, в каком городе больше всего клиентов, а затем использовать результат в другом запросе, чтобы найти всех клиентов из этого города:
Котлин
// 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()) { ... } } } }
Ява
// 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()) { ... } } } }
Чтобы получить результат вдвое быстрее предыдущего примера, используйте один SQL-запрос с вложенными операторами:
Котлин
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()) { ... } }
Ява
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()) { ... } }
Проверка уникальности в SQL
Если строку нельзя вставить, если конкретное значение столбца не является уникальным в таблице, то может быть более эффективным обеспечить эту уникальность с помощью ограничения столбца.
В следующем примере один запрос выполняется для проверки вставляемой строки, а другой — для фактической вставки:
Котлин
// 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 ) )
Ява
// 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, });
Вместо проверки ограничения уникальности в Kotlin или Java вы можете проверить его в SQL при определении таблицы:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
username TEXT UNIQUE
);
SQLite делает то же самое, что и следующее:
CREATE TABLE Customers(...);
CREATE UNIQUE INDEX CustomersUsername ON Customers(username);
Теперь вы можете вставить строку и позволить SQLite проверить ограничение:
Котлин
try { db.execSql( "INSERT INTO Customers VALUES (?, ?, ?)", arrayOf(customer.id.toString(), customer.name, customer.username) ) } catch(e: SQLiteConstraintException) { throw AddCustomerException(customer, e) }
Ява
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 поддерживает уникальные индексы с несколькими столбцами:
CREATE TABLE table(...);
CREATE UNIQUE INDEX unique_table ON table(column1, column2, ...);
SQLite проверяет ограничения быстрее и с меньшими затратами, чем код Kotlin или Java. Рекомендуется использовать SQLite вместо кода приложения.
Пакетная вставка нескольких записей в одной транзакции
Транзакция фиксирует несколько операций, что повышает не только эффективность, но и корректность. Для повышения согласованности данных и ускорения производительности можно выполнять пакетные вставки:
Котлин
db.beginTransaction() try { customers.forEach { customer -> db.execSql( "INSERT INTO Customers VALUES (?, ?, ...)", arrayOf(customer.id.toString(), customer.name, ...) ) } } finally { db.endTransaction() }
Ява
db.beginTransaction(); try { for (customer : Customers) { db.execSQL( "INSERT INTO Customers VALUES (?, ?, ...)", new String[] { String.valueOf(customer.id), customer.name, ... }); } } finally { db.endTransaction() }
Используйте инструменты устранения неполадок
SQLite предоставляет следующие инструменты устранения неполадок, помогающие измерять производительность.
Используйте интерактивную подсказку SQLite
Запустите SQLite на своём компьютере, чтобы выполнять запросы и учиться. Разные версии Android используют разные версии SQLite. Чтобы использовать тот же движок, что и на устройстве Android, используйте adb shell
и запустите sqlite3
на целевом устройстве.
Вы можете попросить SQLite замерить время выполнения запросов:
sqlite> .timer on
sqlite> SELECT ...
Run Time: real ... user ... sys ...
EXPLAIN QUERY PLAN
Вы можете попросить SQLite объяснить, как она собирается ответить на запрос, используя EXPLAIN QUERY PLAN
:
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SCAN Customers
В предыдущем примере требуется полное сканирование таблицы без индекса для поиска всех клиентов из Парижа. Это называется линейной сложностью . SQLite необходимо прочитать все строки и оставить только те, которые соответствуют клиентам из Парижа. Чтобы исправить это, можно добавить индекс:
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=?
Если вы используете интерактивную оболочку, вы можете попросить SQLite всегда объяснять планы запросов:
sqlite> .eqp on
Более подробную информацию см. в разделе Планирование запросов .
Анализатор SQLite
SQLite предлагает интерфейс командной строки (CLI) sqlite3_analyzer
для вывода дополнительной информации, которая может быть использована для диагностики и устранения неполадок с производительностью. Чтобы установить SQLite, посетите страницу загрузки .
Вы можете использовать adb pull
для загрузки файла базы данных с целевого устройства на вашу рабочую станцию для анализа:
adb pull /data/data/<app_package_name>/databases/<db_name>.db
Браузер SQLite
Вы также можете установить графический инструмент SQLite Browser на странице загрузок SQLite.
Ведение журнала Android
Android обрабатывает запросы SQLite и регистрирует их для вас:
# Enable query time logging
$ adb shell setprop log.tag.SQLiteTime VERBOSE
# Disable query time logging
$ adb shell setprop log.tag.SQLiteTime ERROR
Трассировка Perfetto
При настройке Perfetto вы можете добавить следующее, чтобы включить треки для отдельных запросов:
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
atrace_categories: "database"
}
}
}
dumpsys meminfo
adb shell dumpsys meminfo <package-name>
выведет статистику использования памяти приложением, включая некоторые сведения о памяти SQLite. Например, вот что взято из вывода команды adb shell dumpsys meminfo com.google.android.gms.persistent
на устройстве разработчика:
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
В разделе DATABASES
вы найдете:
-
pgsz
: размер одной страницы базы данных в КБ. -
dbsz
: размер всей базы данных в страницах. Чтобы получить размер в килобайтах, умножьтеpgsz
наdbsz
. -
Lookaside(b)
: объём памяти, выделяемой для буфера SQLite для каждого соединения, в байтах. Обычно он очень мал. -
cache hits
: SQLite поддерживает кэш страниц базы данных. Это количество обращений к кэшу страниц (счётчик). -
cache misses
: количество промахов кэша страниц (количество). -
cache size
: количество страниц в кэше (count). Чтобы получить размер в килобайтах, умножьте это число наpgsz
. -
Dbname
: путь к файлу базы данных. В нашем примере к имени некоторых баз данных добавлена цифра(1)
или другое число, указывающее на наличие нескольких подключений к одной и той же базе данных. Статистика отслеживается по каждому подключению.
В разделе POOL STATS
вы найдете:
-
cache hits
: SQLite кэширует подготовленные операторы и пытается повторно использовать их при выполнении запросов, чтобы сэкономить усилия и память при компиляции операторов SQL. Это количество попаданий в кэш операторов (count). -
cache misses
: количество промахов кэша операторов (количество). -
cache size
: начиная с Android 17, здесь отображается общее количество подготовленных операторов в кэше. В более ранних версиях это значение эквивалентно сумме попаданий и промахов, указанных в двух других столбцах, и не отражает размер кэша.
Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Выполнение бенчмарков в непрерывной интеграции
- Замороженные кадры
- Создавайте и измеряйте базовые профили без Macrobenchmark