Почему N+1 запросы убивают производительность?
Мы часто сталкиваемся с ситуацией, когда загружается список заказов, а потом для каждого выполняется отдельный запрос за пользователем. 100 заказов = 101 запрос к SQLite. Это классический N+1 запрос. При 1000 записей это 1001 запрос, каждый из которых может занимать 5–10 мс, итого 5–10 секунд блокировки UI. CoreData решает через relationshipKeyPathsForPrefetching, Room — через @Relation с @Transaction. Flutter + sqflite — JOIN-запрос вместо вложенного цикла. Типичная ошибка: разработчики не смотрят на количество запросов в логе. Результат — приложение тормозит, а проблема в нескольких строках кода.
Как индексы ускоряют запросы в 10 раз?
SQLite под капотом у CoreData, Room и большинства мобильных ORM. WHERE по неиндексированному полю на таблице с 50 000 строк делает full scan. На Android Room добавляем @Index к сущности, на iOS CoreData — выставляем indexed в Data Model Inspector. Разница в скорости выборки — десятки раз. Без индекса запрос может длиться 500 мс, с индексом — 5 мс. Типичный пример: поиск товара по названию на таблице из 200 000 строк без индекса занимает 1.8 секунды, с индексом — 40 мс. Всегда добавляйте индексы на поля, участвующие в WHERE, JOIN и ORDER BY.
Решения по платформам
iOS — CoreData
NSPersistentContainer даёт newBackgroundContext() для фоновых операций. Правильная схема использует фоновые контексты:
container.performBackgroundTask { context in // массовые операции здесь try? context.save() DispatchQueue.main.async { // обновление UI } } NSFetchRequest.fetchBatchSize = 20 — CoreData загружает данные порциями по мере обращения, а не всё сразу. NSFetchedResultsController с sectionNameKeyPath для таблиц с секциями — правильный паттерн, который автоматически обновляет UITableView при изменении данных.
Для bulk insert NSBatchInsertRequest (iOS 13+) работает напрямую в SQLite без создания managed objects — в 10–20 раз быстрее стандартного insert для тысяч записей.
Android — Room
@Query с EXPLAIN QUERY PLAN через adb shell — быстрый способ увидеть, есть ли full scan. Room @TypeConverter для JSON-полей через Gson / Moshi работает, но тормозит на массовых выборках — нормализуйте данные.
Flow<List<Entity>> из Room автоматически эмитит новые данные при изменении таблицы — не нужно вручную инвалидировать кэш. distinctUntilChanged() предотвращает лишние эмиссии если данные не изменились.
Room.databaseBuilder().setQueryCoroutineContext(Dispatchers.IO) — явно указываем, что Room-запросы идут на IO-диспетчере.
Flutter — Drift
Drift (бывший Moor) — предпочтительный выбор для сложных схем: типобезопасные запросы, миграции, генерация кода. database.transaction() для batch-операций — в транзакции 1000 INSERT выполняются за 50–100 мс, без транзакции — 5–10 секунд (каждый INSERT открывает/закрывает транзакцию SQLite).
Как FTS помогает при поиске по 200 000 записей?
Из нашей практики: приложение для offline-каталога товаров — поиск по названию на Room без FTS занимал 1.8 секунды. Подключили FTS4:
@Fts4 @Entity(tableName = "products_fts") data class ProductFts(val name: String, val description: String) MATCH по FTS-таблице — 40–60 мс на том же датасете. Как отмечают разработчики SQLite, FTS4 обеспечивает полнотекстовый поиск за миллисекунды. Для iOS CoreData используйте NSPredicate с MATCH через SQLite напрямую, если нужно быстрый полнотекстовый поиск.
Как правильно оптимизировать базу данных мобильного приложения?
Шаг 1: Аудит схемы
Выявите таблицы без индексов и проблемные запросы с помощью EXPLAIN QUERY PLAN.
Шаг 2: Добавление индексов
На поля, участвующие в WHERE, JOIN и ORDER BY, создайте индексы.
Шаг 3: Устранение N+1 запросов
Используйте prefetching в CoreData или JOIN в Room/Drift.
Шаг 4: Batch-операции
Для массовых вставок применяйте NSBatchInsertRequest, @Insert с аннотацией в Room или транзакции в Drift.
Шаг 5: Тестирование производительности
Замерьте время запросов до и после, убедитесь в отсутствии блокировок UI.
Сравнение производительности до и после оптимизации
| Запрос | До оптимизации | После оптимизации |
|---|---|---|
| Поиск по названию (200k записей) | 1.8 с (LIKE) | 40 мс (FTS4) |
| Загрузка списка заказов с пользователями (N+1) | 1.1 с (101 запрос) | 20 мс (1 JOIN) |
| Массовая вставка 10 000 записей | 15 с (по одной) | 600 мс (batch) |
Пример кода для batch-вставки в Drift
await database.transaction(() async { for (final item in items) { await database.insert(item); } }); Что входит в оптимизацию?
| Этап | Что делаем | Длительность |
|---|---|---|
| Аудит схемы | Анализ таблиц, индексов, запросов | 1-2 дня |
| Оптимизация | Индексы, batch-операции, фоновые контексты | 3-7 дней |
| Тестирование | Проверка производительности и регрессии | 1-2 дня |
| Документация | Рекомендации по мониторингу | включено |
Наш опыт — 5 лет на рынке мобильной разработки, более 50 проектов с оптимизацией БД. Мы гарантируем измеримый прирост производительности. Свяжитесь с нами, чтобы получить консультацию и расчёт стоимости. Закажите аудит уже сегодня и получите детальный отчёт с рекомендациями.







