Ошибки при заблокированной базе данных

Библиотека idb-keyval предоставляет удобный интерфейс для работы с IndexedDB, скрывая сложность низкоуровневого API. Однако при работе с этой библиотекой могут возникать ошибки, связанные с блокировкой базы данных. Понимание их природы и способов обработки критически важно для надежной работы приложения.

Причины блокировки базы данных

База данных IndexedDB может оказаться заблокированной по нескольким причинам:

  1. Одновременное открытие нескольких версий базы При попытке открыть базу с другой версией в то время, как другая транзакция ещё активна, IndexedDB может заблокировать новую сессию до завершения текущей. В idb-keyval это проявляется как отказы в операциях get, set, del или clear.

  2. Длительные или зависшие транзакции Транзакции IndexedDB ограничены временем жизни. Если одна транзакция слишком долго удерживает блокировку на объектном хранилище, новые операции оказываются в состоянии ожидания или получают ошибку DOMException: The database connection is blocked.

  3. Закрытие вкладки или перезагрузка браузера Если база была открыта в нескольких вкладках, закрытие одной из них во время активной транзакции другой может вызвать сбои при последующих операциях.

  4. Конфликты версий базы данных При обновлении схемы IndexedDB через изменение версии, idb-keyval автоматически вызывает событие onupgradeneeded. Если другая транзакция в этот момент работает с базой, попытка изменения версии приведёт к блокировке.

Типы ошибок

При блокировке базы данных чаще всего встречаются следующие ошибки:

  • DOMException: The database connection is blocked Возникает при попытке обновления схемы базы, когда открытые соединения еще не закрыты.

  • DOMException: Version change transaction blocked Появляется, если происходит попытка обновления версии базы через idb-keyval в момент активных транзакций.

  • AbortError Транзакция прерывается из-за блокировки другой транзакцией или ошибки браузера.

  • QuotaExceededError Иногда блокировка базы сопровождается этой ошибкой, если попытка записи превышает доступное пространство.

Обработка ошибок

idb-keyval возвращает промисы для всех операций (get, set, del, clear). Это позволяет использовать стандартные методы обработки ошибок:

import { set, get } from 'idb-keyval';

set('user', { name: 'Alice' })
  .catch(err => {
    if (err.name === 'AbortError' || err.name === 'QuotaExceededError') {
      console.error('Ошибка транзакции или блокировка базы', err);
    } else {
      throw err;
    }
  });

get('user')
  .then(value => console.log(value))
  .catch(err => console.error('Не удалось получить значение:', err));

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

async function safeSet(key, value, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      await set(key, value);
      return;
    } catch (err) {
      if (err.name === 'AbortError') {
        await new Promise(res => setTimeout(res, 100));
        continue;
      }
      throw err;
    }
  }
  throw new Error('Не удалось записать данные после нескольких попыток');
}

Рекомендации по предотвращению блокировок

  • Минимизировать длительные транзакции. Все операции с большим объемом данных следует делить на несколько коротких транзакций.
  • Закрывать неиспользуемые соединения. Для этого idb-keyval позволяет управлять базой через кастомный объект Store.
  • Обрабатывать события versionchange. Если обнаружено событие изменения версии базы, следует корректно закрыть соединения.
  • Использовать отложенные операции. При высокой вероятности конфликта лучше ставить операции в очередь и выполнять их последовательно.

Практика с idb-keyval

Создание собственного хранилища с обработкой ошибок выглядит следующим образом:

import { Store, set, get } from 'idb-keyval';

const myStore = new Store('my-database', 'keyval-store');

async function writeData(key, value) {
  try {
    await set(key, value, myStore);
  } catch (err) {
    console.warn('Ошибка записи в базу:', err.name);
    // Возможна повторная попытка или логирование
  }
}

async function readData(key) {
  try {
    return await get(key, myStore);
  } catch (err) {
    console.warn('Ошибка чтения из базы:', err.name);
    return null;
  }
}

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