Индексная модель Dexie.js строится поверх возможностей IndexedDB и опирается на три ключевых механизма: первичные ключи, вторичные индексы и курсоры. Именно их взаимодействие определяет, насколько эффективно выполняются запросы, как устроена сортировка данных и какие операции доступны при обходе коллекций.
Каждая запись в таблице Dexie.js обязана иметь первичный ключ. Он обеспечивает уникальность и служит основой для всех внутренних операций хранения и выборки.
Схема таблицы задаётся строкой, где первичный ключ обозначается специальными префиксами:
++ — автоинкрементный ключ& — уникальный индекс (может быть использован как
альтернативный ключ, но не первичный)Пример:
db.version(1).stores({
users: '++id, name, age'
});
Здесь id — первичный ключ, который автоматически
увеличивается при каждой вставке.
Dexie.js поддерживает составные ключи:
db.version(1).stores({
orders: '[userId+orderId], amount, createdAt'
});
В этом случае первичный ключ формируется из комбинации
userId и orderId. Такой подход полезен при
моделировании данных, где идентичность определяется контекстом, а не
одним полем.
Особенности составного ключа:
Индекс в Dexie.js — это отдельная структура, ускоряющая доступ к данным по определённому полю или комбинации полей. Индексы устраняют необходимость полного сканирования таблицы при фильтрации.
Объявление индексов происходит в схеме:
db.version(1).stores({
users: '++id, &email, name, age, *tags, [lastName+firstName]'
});
Здесь присутствуют разные типы индексов:
&email — уникальный индексname, age — обычные индексы*tags — multiEntry индекс[lastName+firstName] — составной индексУникальный индекс гарантирует, что значения в поле не повторяются:
&email
Попытка вставить запись с уже существующим email приведёт к ошибке. Это поведение аналогично уникальным ограничениям в реляционных базах данных.
Обычные индексы создаются без префикса:
name, age
Они ускоряют операции поиска и сортировки, но допускают дубликаты значений.
MultiEntry индекс (*) используется для массивов. Каждый
элемент массива индексируется отдельно:
tags: '*tags'
Если запись содержит:
{
id: 1,
tags: ['js', 'indexeddb', 'storage']
}
то индекс создаёт три записи в индексной структуре. Это позволяет выполнять запросы по отдельным элементам массива:
db.users.where('tags').equals('js')
Особенность multiEntry индексов заключается в расширении одного объекта в несколько индексных записей, что повышает гибкость поиска, но увеличивает объём индекса.
Составной индекс объявляется через []:
'[lastName+firstName]'
Он используется для:
Dexie.js (через IndexedDB) сравнивает составные ключи последовательно:
lastName;firstName.Это означает, что запросы эффективно работают только при соблюдении левой части ключа (leftmost prefix rule).
Пример:
db.users.where('[lastName+firstName]')
.between(['Ivanov', 'A'], ['Ivanov', 'Z']);
Такой запрос корректно использует индекс. Однако запрос только по
firstName индекс использовать не сможет.
Dexie.js предоставляет API where, которое автоматически
выбирает индекс, если он существует.
db.users.where('age').equals(25);
Используется индекс age, если он объявлен.
db.users.where('age').above(18);
db.users.where('age').below(60);
db.users.where('age').between(18, 60);
Диапазонные операции выполняются через индексный обход, что значительно быстрее полного сканирования.
db.users.where('age').anyOf([20, 25, 30]);
Индекс используется для нескольких точек поиска без линейного прохода.
Метод orderBy использует индекс напрямую:
db.users.orderBy('age');
Если индекс существует, данные возвращаются уже отсортированными, без дополнительной обработки.
Дополнительные операции:
db.users.orderBy('age').reverse();
Важно, что orderBy работает только по индексируемым
полям. Попытка сортировки по неиндексированному полю приводит к полному
сканированию таблицы.
Внутри IndexedDB данные перебираются с помощью курсоров. Dexie.js абстрагирует этот механизм через коллекции, но концепция остаётся ключевой.
Курсор — это указатель на текущую запись в индексированной структуре, который перемещается последовательно.
Dexie предоставляет несколько методов обхода:
db.users.each(user => {
console.log(user);
});
Этот метод использует курсор под капотом и последовательно перебирает записи.
Аналогичные методы:
eachKey() — перебор только ключейeachPrimaryKey() — перебор первичных ключейeachUniqueKey() — перебор значений индекса без
дубликатовDexie.js формирует коллекции через where и
orderBy. Каждая коллекция может быть преобразована в поток
курсора.
Пример:
db.users.where('age').above(18).each(user => {
console.log(user.name);
});
На уровне IndexedDB это превращается в:
age;> 18;Курсоры позволяют эффективно реализовывать пагинацию:
db.users.orderBy('id').offset(20).limit(10).toArray();
offset — пропуск первых элементов;limit — ограничение количества записей.Хотя offset удобен, он становится менее эффективным на
больших наборах данных, поскольку курсор всё равно проходит пропущенные
элементы.
Dexie.js позволяет инвертировать курсор:
db.users.orderBy('age').reverse().each(user => {
console.log(user.age);
});
Это не требует дополнительной сортировки — индекс просто читается в обратном направлении.
При изменении записи IndexedDB автоматически:
Dexie.js скрывает этот процесс, но он влияет на производительность при массовых обновлениях.
Метод filter выполняет пост-обработку:
db.users.filter(user => user.age > 18)
Такой код:
where.Правильный подход:
db.users.where('age').above(18)
Некорректная модель индексов приводит к деградации производительности:
filter вместо
where.Особенно критична ошибка с составными ключами: если порядок полей не соответствует запросам, индекс фактически становится бесполезным.
Dexie.js транслирует запросы в IndexedDB следующим образом:
При этом:
Эффективные паттерны:
where;orderBy;limit;each вместо toArray при
потоковой обработке.Пример комбинированного подхода:
db.orders
.where('createdAt')
.between(startDate, endDate)
.orderBy('createdAt')
.reverse()
.limit(100)
.each(order => process(order));
Такой запрос задействует индекс, курсор и ограничение выборки без лишних операций в памяти.
При увеличении объёма данных эффективность индексов становится определяющим фактором:
Dexie.js не изменяет эту модель, но предоставляет инструменты для её правильного использования через декларативные запросы и управление коллекциями.