Dexie.js строится поверх IndexedDB и наследует его транзакционную модель, в которой все операции чтения и записи выполняются внутри транзакций. Транзакция определяет границы атомарности, доступ к объектным хранилищам и уровень прав доступа к данным.
В Dexie.js транзакции представлены двумя основными режимами:
Эти режимы определяют поведение блокировок, возможность изменения данных и жизненный цикл операции внутри базы данных.
Транзакция рассматривается как единый блок работы:
Это особенно важно для операций, включающих несколько таблиц или последовательных изменений.
Транзакция в Dexie.js всегда привязана к набору таблиц:
db.transaction('rw', db.users, db.orders, async () => {
// доступ к users и orders
});
Если внутри транзакции происходит обращение к таблице, не включённой в список, возникает ошибка.
readonly предназначен исключительно для операций чтения
данных. Любая попытка изменения данных внутри такой транзакции приводит
к исключению.
Характерные свойства:
Внутри IndexedDB readonly-транзакции:
Dexie.js использует этот режим по умолчанию для всех операций чтения вне явно заданной транзакции.
db.transaction('r', db.users, async () => {
const allUsers = await db.users.toArray();
console.log(allUsers);
});
db.transaction('readonly', db.orders, async () => {
const pending = await db.orders
.where('status')
.equals('pending')
.toArray();
});
Readonly транзакции позволяют IndexedDB:
Особенно это заметно при массовых запросах:
await db.transaction('r', db.products, db.categories, async () => {
const products = await db.products.toArray();
const categories = await db.categories.toArray();
});
readwrite используется для операций, которые изменяют
состояние базы данных:
Этот режим требует эксклюзивного доступа к задействованным таблицам.
В режиме readwrite IndexedDB накладывает строгие
ограничения:
Dexie.js автоматически управляет этими блокировками, скрывая низкоуровневую сложность.
db.transaction('rw', db.users, async () => {
await db.users.add({
name: 'Alex',
age: 32
});
});
Readwrite транзакции позволяют выполнять сложные сценарии:
db.transaction('rw', db.orders, db.inventory, async () => {
const order = await db.orders.get(1);
await db.inventory.where('productId')
.equals(order.productId)
.modify(item => {
item.stock -= order.quantity;
});
await db.orders.update(1, { status: 'processed' });
});
Readonly транзакции обычно быстрее, так как:
Readwrite транзакции медленнее из-за:
// могут выполняться параллельно
db.transaction('r', db.users, async () => {});
db.transaction('r', db.users, async () => {});
// могут конфликтовать
db.transaction('rw', db.users, async () => {});
db.transaction('rw', db.users, async () => {});
Dexie.js автоматически создаёт транзакции, если разработчик их явно не указывает.
await db.users.toArray();
await db.users.get(1);
Любые операции чтения выполняются в скрытой readonly транзакции.
await db.users.add({ name: 'Maria' });
await db.users.put({ id: 1, name: 'Updated' });
await db.users.delete(3);
Каждая операция записи запускается в отдельной readwrite транзакции, если не объединена вручную.
Явное управление транзакциями позволяет:
await db.transaction('rw', db.users, db.orders, async () => {
const user = await db.users.get(1);
await db.orders.add({
userId: user.id,
total: 250
});
await db.users.update(user.id, {
lastOrderDate: new Date()
});
});
Если одна из операций завершается ошибкой, вся транзакция откатывается.
Любая ошибка внутри readwrite транзакции приводит к её отмене:
db.transaction('rw', db.users, async () => {
await db.users.add({ id: 1 });
throw new Error('failure');
});
Результат:
Readonly транзакции не допускают изменений, поэтому ошибки возникают при попытке записи:
db.transaction('r', db.users, async () => {
await db.users.add({ name: 'Invalid' }); // ошибка
});
Dexie.js не поддерживает полноценные вложенные транзакции как независимые сущности. Вместо этого:
db.transaction('rw', db.users, async () => {
await db.users.add({ name: 'A' });
await Promise.resolve().then(() =>
db.users.add({ name: 'B' })
);
});
Все операции остаются в рамках одной транзакции, если не происходит выхода из контекста.
Транзакции в Dexie.js завершаются автоматически:
Readwrite транзакция удерживается активной до завершения всех await-операций внутри её области.
Наиболее частая ошибка:
db.transaction('r', db.users, async () => {
await db.users.put({ id: 1, name: 'Test' });
});
db.transaction('rw', db.users, async () => {
await db.orders.add({}); // ошибка
});
await db.users.add({ name: 'A' });
await db.orders.add({ userId: 1 });
Проблема: отсутствие атомарности между операциями.
Readonly используется для:
Readwrite используется для:
Разделение на readonly и readwrite транзакции формирует структуру взаимодействия с данными:
Такое разделение снижает вероятность гонок данных и повышает предсказуемость состояния IndexedDB.