Метод primaryKeys() и keys()

Методы keys() и primaryKeys() в Dexie.js используются для получения ключей записей из таблицы или запроса, но работают на разных уровнях индексации и дают принципиально различный результат по смыслу и производительности.

Оба метода возвращают массив или поток значений ключей, однако важно понимать, какие именно ключи извлекаются: либо ключи индекса/курсорного обхода, либо первичные ключи объектов. Это различие критично при работе с большими наборами данных и составными индексами.


Метод keys() возвращает значения индекса, по которому выполняется обход запроса. Это означает, что результат зависит от того, как именно сформирован запрос: используется ли where(), orderBy(), или полный перебор таблицы.

При вызове на таблице без индексового ограничения keys() возвращает первичные ключи записей, но при использовании индекса — значения именно этого индекса.

const db = new Dexie("MyDB");

db.version(1).stores({
  users: "++id, name, age"
});

// получение ключей (по первичному ключу)
const ids = await db.users.toCollection().keys();

При работе через индекс:

const names = await db.users.where("age").above(18).keys();

В этом случае возвращаются значения индекса age, а не id.

Особенности keys()

  • Возвращает ключи текущего курсора
  • Зависит от выбранного индекса или условия where
  • Может возвращать дубликаты, если индекс не уникален
  • Поддерживает Collection и Table
  • Работает быстрее, чем toArray(), так как не извлекает полные объекты

primaryKeys(): строго первичные ключи записей

Метод primaryKeys() всегда возвращает именно первичные ключи таблицы, независимо от того, какой индекс используется в запросе.

Это делает его предсказуемым инструментом при любых фильтрациях и сортировках.

const db = new Dexie("MyDB");

db.version(1).stores({
  users: "++id, name, age"
});

// первичные ключи всех записей
const ids = await db.users.toCollection().primaryKeys();

Даже если запрос выполняется по индексу:

const ids = await db.users.where("age").above(18).primaryKeys();

результатом всё равно будут значения id, а не age.

Особенности primaryKeys()

  • Всегда возвращает первичный ключ таблицы
  • Не зависит от используемого индекса
  • Удобен для последующих операций bulkDelete, bulkGet
  • Работает быстрее, чем получение объектов целиком
  • Предсказуем при сложных запросах с where, orderBy, filter

Сравнение поведения на индексах

Рассмотрим таблицу:

db.version(1).stores({
  users: "++id, name, age"
});

Данные:

id name age
1 Alex 20
2 Bob 30
3 Carol 30

Запрос:

```javascript
const res1 = await db.users.where("age").equals(30).keys();
const res2 = await db.users.where("age").equals(30).primaryKeys();

Результат:

  • keys()[30, 30]
  • primaryKeys()[2, 3]

Это демонстрирует фундаментальную разницу: keys() работает на уровне индекса age, а primaryKeys() — на уровне сущностей таблицы.


Производительность и внутренний механизм

Оба метода используют курсоры IndexedDB, но различаются тем, какие данные извлекаются из транзакции.

  • keys() читает только ключ курсора (index key)
  • primaryKeys() делает дополнительное разрешение к primary key при обходе индекса

Несмотря на это, primaryKeys() всё равно значительно быстрее, чем toArray(), так как не сериализует весь объект.

При больших выборках разница становится особенно заметной:

// медленнее — полные объекты
await db.users.where("age").above(18).toArray();

// быстрее — только первичные ключи
await db.users.where("age").above(18).primaryKeys();

Поведение в Collection API

Оба метода принадлежат API Collection, поэтому применимы после where(), filter(), limit(), offset().

db.users
  .where("age")
  .above(18)
  .limit(10)
  .keys();

db.users
  .where("age")
  .above(18)
  .limit(10)
  .primaryKeys();

Важно учитывать, что filter() выполняется на уровне JavaScript после извлечения курсора, поэтому ключи уже получены и просто преобразуются.


Сортировка и orderBy()

При использовании orderBy() поведение также различается:

db.users.orderBy("age").keys();
db.users.orderBy("age").primaryKeys();
  • keys() → значения age в отсортированном порядке
  • primaryKeys()id записей в порядке сортировки по age

Это особенно важно при построении пагинации, где требуется сохранять стабильный порядок первичных идентификаторов.


Составные индексы

При работе с compound index:

db.version(1).stores({
  orders: "++id, [userId+createdAt]"
});
db.orders.orderBy("[userId+createdAt]").keys();

Результатом будут кортежи индекса:

  • [userId, createdAt]

А:

db.orders.orderBy("[userId+createdAt]").primaryKeys();

вернёт только id записей.


Использование в массовых операциях

Метод primaryKeys() часто применяется как подготовительный шаг для bulk-операций:

const ids = await db.users.where("age").above(18).primaryKeys();

await db.users.bulkDelete(ids);

Такой подход позволяет избежать загрузки полных объектов, минимизируя память и время обработки.


Отличия от toArray()

Метод Что возвращает Нагрузка
toArray() Полные объекты высокая
keys() ключи индекса низкая
primaryKeys() первичные ключи низкая–средняя

При необходимости передачи данных между слоями приложения выбор между keys() и primaryKeys() становится архитектурным решением: либо работать с индексами, либо с сущностями таблицы.


Поведение при пустых результатах

Оба метода возвращают пустой массив при отсутствии совпадений, без исключений:

const keys = await db.users.where("age").above(200).keys();
// []
const ids = await db.users.where("age").above(200).primaryKeys();
// []

Влияние уникальности индекса

Если индекс не уникален, keys() может возвращать повторяющиеся значения:

db.users.where("age").keys(); // [30, 30, 30]

primaryKeys() всегда возвращает уникальные идентификаторы записей, поскольку опирается на первичный ключ:

// [2, 3, 5]

Сводное поведение в запросах

  • keys() — отражает структуру индекса запроса
  • primaryKeys() — стабильно отражает идентичность записей
  • оба метода работают поверх курсорной модели IndexedDB
  • различие становится критическим при использовании where, orderBy, составных индексов и bulk-операций