Удаление записей: delete()

Удаление данных в Dexie.js строится поверх транзакций IndexedDB и обеспечивает несколько уровней работы: удаление по первичному ключу, массовое удаление через запросы и удаление целых коллекций результатов. Основной метод для удаления одиночной записи — table.delete().


Удаление по первичному ключу: table.delete()

Базовый сценарий удаления — работа с первичным ключом таблицы.

await db.users.delete(1);

В этом примере удаляется запись из таблицы users, у которой первичный ключ равен 1.

Поведение метода

  • возвращает Promise<void>
  • не возвращает удалённый объект
  • не вызывает ошибку, если запись не найдена
  • операция всегда асинхронная
  • выполняется в рамках транзакции IndexedDB

Важно, что отсутствие записи не считается исключительной ситуацией. Удаление считается успешным даже если ключ отсутствует в хранилище.


Транзакционная природа delete()

Любая операция удаления в Dexie.js выполняется внутри транзакции. Если delete() вызывается вне явной транзакции, Dexie создаёт её автоматически:

await db.transaction('rw', db.users, async () => {
    await db.users.delete(1);
});

Особенности транзакции

  • гарантирует атомарность операции
  • блокирует таблицу на время выполнения
  • откатывает изменения при ошибке внутри транзакции
  • позволяет комбинировать удаление с чтением и записью

Удаление внутри транзакции особенно важно при сложных бизнес-операциях, где требуется согласованность данных.


Массовое удаление через where().delete()

Dexie.js поддерживает удаление набора записей через запросы.

await db.users
    .where('age')
    .below(18)
    .delete();

Поведение

  • удаляет все записи, удовлетворяющие условию
  • работает через индекс, если поле индексировано
  • возвращает Promise<void>
  • выполняется быстрее при наличии индекса

Пример с диапазоном

await db.orders
    .where('createdAt')
    .below(Date.now() - 30 * 24 * 60 * 60 * 1000)
    .delete();

Удаление старых записей таким способом часто используется для очистки кэша или логов.


Удаление через collection.delete()

Любой результат запроса (Collection) может быть полностью удалён:

const oldUsers = db.users.where('status').equals('inactive');
await oldUsers.delete();

Отличие от where().delete()

Фактически collection.delete() и where(...).delete() выполняют одинаковую роль, но Collection позволяет:

  • комбинировать несколько условий
  • применять сортировку и фильтры
  • использовать .and(), .or() логические цепочки

Пример сложного удаления:

await db.users
    .where('age').above(30)
    .and(user => user.isBlocked === true)
    .delete();

Удаление по диапазонам ключей

Dexie.js поддерживает удаление через диапазоны ключей IndexedDB:

await db.logs
    .where('id')
    .between(1000, 2000)
    .delete();

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

Также можно использовать крайние границы:

await db.logs
    .where('id')
    .above(5000)
    .delete();

Поведение индексов при удалении

При удалении записи Dexie автоматически:

  • удаляет запись из object store
  • очищает все связанные индексы
  • обновляет secondary indexes синхронно в рамках транзакции

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


Ограничения и особенности IndexedDB

Удаление в Dexie.js наследует ограничения IndexedDB:

  • нет каскадного удаления между таблицами
  • нет внешних ключей
  • каждая таблица изолирована
  • производительность зависит от индексов и размера хранилища

Если требуется каскадное удаление, его необходимо реализовывать вручную:

await db.transaction('rw', db.users, db.posts, async () => {
    await db.posts.where('userId').equals(1).delete();
    await db.users.delete(1);
});

Обработка ошибок при удалении

Типичные ситуации, которые могут привести к ошибкам:

Ошибка транзакции

TransactionInactiveError

Возникает при попытке выполнить удаление вне активной транзакции в специфических сценариях (например, после завершения async-цепочки).

Ошибки индекса

Если запрос использует несуществующий индекс:

db.users.where('nonExistingField').delete();

Dexie выбросит ошибку на этапе построения запроса.


Производительность delete()

Удаление по первичному ключу:

  • O(1) операция
  • самая быстрая форма удаления

Удаление через where():

  • зависит от индекса
  • может быть O(log n + k)
  • при отсутствии индекса выполняется полное сканирование

Рекомендации по производительности:

  • использовать индексы для фильтров удаления
  • избегать сложных .and() при больших наборах данных
  • разделять большие удаления на батчи при необходимости

Удаление в цепочках промисов

Dexie полностью поддерживает async/await, но также работает с цепочками:

db.users.delete(1)
    .then(() => db.users.delete(2))
    .then(() => console.log('done'));

Хотя такой стиль допустим, транзакционный подход предпочтительнее:

await db.transaction('rw', db.users, async () => {
    await db.users.delete(1);
    await db.users.delete(2);
});

Взаимодействие delete() с хуками Dexie

Dexie поддерживает hooks, которые могут перехватывать операции удаления:

db.users.hook('deleting', (primKey, obj, transaction) => {
    console.log('Удаляется:', primKey);
});

Особенности hooks

  • вызываются перед удалением
  • позволяют модифицировать поведение транзакции
  • могут использоваться для логирования
  • не предназначены для асинхронной логики (в большинстве случаев)

Удаление больших объёмов данных

При массовом удалении важно учитывать ограничения памяти и времени выполнения:

await db.logs
    .where('level')
    .equals('debug')
    .delete();

Если объём данных очень большой, возможен подход пакетного удаления:

while (true) {
    const count = await db.logs
        .where('level')
        .equals('debug')
        .limit(1000)
        .delete();

    if (!count) break;
}

Особенности поведения при конкурентных операциях

IndexedDB сериализует транзакции, поэтому:

  • параллельные delete() могут ожидать друг друга
  • порядок выполнения зависит от очереди транзакций
  • конфликтов записи как в SQL нет, но возможны задержки блокировок

Влияние version() и схемы базы

Удаление не зависит от версии схемы напрямую, но при миграциях:

  • старые данные могут удаляться через upgrade-скрипты
  • структура индексов влияет на эффективность where().delete()
  • при изменении схемы важно учитывать существующие ключи

Поведение delete() при отсутствии данных

await db.users.delete(999999);
  • операция завершается успешно
  • исключений не возникает
  • состояние базы не меняется
  • Promise резолвится без значения

Это поведение упрощает написание idempotent-логики, где удаление может выполняться без предварительной проверки существования записи.