В Dexie.js каждая одиночная операция с таблицей автоматически выполняется внутри транзакционного контекста, даже если транзакция явно не создавалась. Такой подход основан на модели IndexedDB, где любая операция чтения или записи уже привязана к транзакции, но библиотека расширяет эту модель, делая её прозрачной для разработчика.
Автоматическая транзакция создаётся для каждого вызова методов уровня таблицы:
get()put()add()update()delete()toArray()count()each()Каждый такой вызов оборачивается в транзакцию с минимально
необходимым набором режимов доступа (readonly или
readwrite) и с максимально узкой областью влияния.
При выполнении одиночной операции Dexie формирует транзакцию, содержащую только одну таблицу (или набор таблиц, если это требуется внутренней реализацией индексов). Это означает:
transaction().Такой подход обеспечивает предсказуемость поведения и снижает вероятность ошибок, связанных с забытым коммитом или преждевременным завершением транзакции.
Пример логики:
db.users.get(1)
Фактически превращается в:
readonly транзакцииDexie выбирает режим транзакции на основе операции:
Readonly транзакции:
gettoArraycountwhere().first()Readwrite транзакции:
putaddupdatedeleteЭто разделение важно, поскольку IndexedDB строго различает режимы
доступа. Попытка записи в readonly транзакции приводит к
ошибке уровня движка.
Dexie автоматически подбирает корректный режим, исключая необходимость ручной настройки.
Автоматическая транзакция в Dexie проходит несколько фаз:
Инициализация
Выполнение операции
Завершение
Откат (при ошибке)
Важно, что даже одиночная ошибка внутри операции приводит к откату всей транзакции, несмотря на её минимальный размер.
Dexie тесно связывает IndexedDB транзакции с Promise-цепочками. Это означает, что транзакция живёт до тех пор, пока не завершится асинхронная цепочка выполнения.
db.users.get(1).then(user => {
return db.orders.where('userId').equals(user.id).toArray();
});
В этом случае:
get) выполняется в автоматической
транзакцииЭто ключевое ограничение автоматических транзакций: они не распространяются между независимыми Promise.
Автоматическая транзакция всегда ограничена одной операцией. Даже если метод выглядит как цепочка, каждая часть может инициировать новую транзакцию.
Пример:
db.users.get(1)
.then(user => db.orders.where('userId').equals(user.id).toArray());
Здесь создаются две независимые транзакции:
getwhere().toArray()Это может приводить к состояниям, где данные между операциями уже изменились.
Dexie предоставляет явные транзакции через
db.transaction():
db.transaction('rw', db.users, db.orders, async () => {
const user = await db.users.get(1);
await db.orders.add({ userId: user.id });
});
Различие принципиальное:
| Автоматическая транзакция | Явная транзакция |
|---|---|
| одна операция | несколько операций |
| короткий жизненный цикл | управляемый цикл |
| создаётся автоматически | создаётся вручную |
| нет гарантии атомарности | полная атомарность |
Автоматические транзакции предназначены для простых операций, где не требуется координация нескольких шагов.
При одновременном выполнении нескольких операций Dexie создаёт отдельные транзакции для каждой:
db.users.get(1);
db.users.put({ id: 2, name: 'A' });
db.users.delete(3);
Каждый вызов:
Это улучшает производительность, но исключает атомарность между вызовами.
Dexie использует ряд оптимизаций при автоматическом создании транзакций:
Ключевой принцип: транзакция должна быть максимально короткой, чтобы снизить блокировки IndexedDB.
Ошибки в автоматических транзакциях имеют локальный характер:
get() завершает только эту
транзакциюПример:
db.users.add({ id: 1 }).catch(err => console.error(err));
Если операция нарушает constraint (например, дубликат ключа), транзакция откатывается полностью, но только в рамках этой операции.
Dexie управляет автоматическими транзакциями через внутренний слой обёрток над IndexedDB:
Dexie.currentTransaction отслеживает активный
контекстЭтот механизм обеспечивает единообразие API независимо от того, используется ли явная транзакция.
Автоматические транзакции гарантируют:
Однако они не гарантируют согласованность между несколькими операциями, выполненными последовательно.
Это особенно важно при работе с:
В таких случаях требуется явная транзакция.
Запросы с индексами и фильтрацией:
db.orders.where('status').equals('pending').toArray();
всё равно выполняются в автоматической транзакции, несмотря на потенциальную сложность сканирования индекса.
Dexie:
Автоматические транзакции формируют базовый уровень абстракции библиотеки. Они позволяют:
При этом они являются лишь нижним уровнем, поверх которого строятся более сложные механизмы управления состоянием и атомарностью операций.