В IndexedDB отсутствует механизм соединений таблиц, аналогичный SQL JOIN, что напрямую влияет на способы проектирования схем данных в Dexie.js и приводит к необходимости выбирать между нормализованной моделью и денормализацией данных.
IndexedDB реализует модель объектного хранилища, в которой каждая таблица (object store) изолирована и оперирует собственными индексами. Межтабличные операции на уровне движка отсутствуют, поэтому любые связи между сущностями не поддерживаются декларативно.
Dexie.js, будучи высокоуровневой обёрткой, сохраняет эту особенность и предоставляет удобный API поверх примитивов IndexedDB, но не добавляет полноценный механизм JOIN. Любая агрегация данных из разных таблиц выполняется на стороне JavaScript, что приводит к дополнительным затратам:
В условиях ограничений браузерного хранилища это становится ключевым фактором проектирования архитектуры данных.
Отсутствие соединений обусловлено архитектурой IndexedDB:
Добавление JOIN на уровне движка потребовало бы значительных накладных расходов и усложнения реализации, особенно в контексте браузерной среды.
Денормализация представляет собой стратегию хранения данных, при которой связанные сущности частично или полностью дублируются в рамках одной записи или нескольких таблиц с целью минимизации необходимости последующих соединений.
Основная идея заключается в переносе стоимости вычислений с этапа чтения на этап записи.
Вместо:
используется:
Один из базовых подходов заключается в копировании наиболее часто используемых полей связанной сущности.
Пример структуры:
db.orders.add({
id: 1,
userId: 10,
userName: "Ivan Petrov",
total: 2500
});
Здесь userName дублируется из таблицы пользователей для
ускорения отображения списка заказов без дополнительного запроса.
При создании записи фиксируется состояние связанной сущности на момент операции.
db.orders.add({
id: 2,
userId: 10,
userSnapshot: {
name: "Ivan Petrov",
email: "ivan@mail.com"
},
items: [
{ productId: 5, title: "Keyboard", price: 1200 }
]
});
Такой подход особенно полезен в доменах, где важна историческая достоверность данных: финансы, заказы, аудит.
Отдельная таблица используется как предрасчитанное представление данных.
db.orderViews.put({
orderId: 1,
userName: "Ivan Petrov",
total: 2500,
itemCount: 3
});
Эта модель фактически имитирует поведение SQL VIEW, но требует ручного обновления при изменениях исходных данных.
Главная проблема денормализации заключается в рассинхронизации копий данных. Dexie.js предоставляет механизмы, позволяющие частично контролировать этот процесс.
Dexie поддерживает hooks, позволяющие реагировать на изменения таблиц:
db.users.hook("updating", (modifications, primKey, obj) => {
if (modifications.name) {
return db.orders
.where("userId")
.equals(primKey)
.modify({ userName: modifications.name });
}
});
Таким образом обеспечивается каскадное обновление дублированных данных.
Использование транзакций позволяет синхронизировать изменения сразу в нескольких таблицах:
db.transaction("rw", db.users, db.orders, async () => {
const userId = 10;
await db.users.update(userId, { name: "Ivan Ivanov" });
await db.orders
.where("userId")
.equals(userId)
.modify({ userName: "Ivan Ivanov" });
});
Транзакция обеспечивает атомарность операции, снижая риск частичной несогласованности.
Альтернативой денормализации остаются ручные соединения данных на уровне Jav * aScript:
const orders = await db.orders.toArray();
const userIds = [...new Set(orders.map(o => o.userId))];
const users = await db.users
.where("id")
.anyOf(userIds)
.toArray();
const userMap = new Map(users.map(u => [u.id, u]));
const result = orders.map(order => ({
...order,
user: userMap.get(order.userId)
}));
Такой подход сохраняет нормализованную модель, но имеет ряд ограничений:
Денормализация устраняет необходимость подобных операций ценой увеличения ответственности за целостность данных.
Выбор между нормализацией и денормализацией в Dexie.js определяется характером нагрузки:
Гибридный подход включает:
Основной акцент делается на скорость чтения. Данные хранятся в форме, максимально близкой к UI:
Данные хранятся в строгой нормализации:
Обновления распространяются через события изменения данных:
Dexie hooks выступают основным механизмом реализации этого подхода.
Особенно критичным является отсутствие централизованной логики обновления, что приводит к постепенному расхождению данных.
Денормализация изменяет баланс нагрузки:
Однако:
В Dexie.js этот компромисс особенно заметен, поскольку все операции выполняются в однопоточном JavaScript-окружении браузера, где стоимость дополнительных обходов коллекций напрямую влияет на отзывчивость интерфейса.