← Назад к вопросам
Как обеспечивал транзакции между двумя базами данных?
2.4 Senior🔥 91 комментариев
#Архитектура и паттерны#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как обеспечивал транзакции между двумя базами данных?
Это классический distributed transaction problem. ACID гарантии работают в одной БД, но между двумя - сложнее.
Проблема:
// Transfer money: remove from user1, add to user2
await db1.users.update({id: user1, balance: balance - 100});
await db2.users.update({id: user2, balance: balance + 100});
// Если первый сработал, второй нет - потеря денег!
Решение 1: Two-Phase Commit (2PC)
1. Prepare phase: обе БД проверяют может ли transaction успешно выполниться
2. Commit phase: обе БД фиксируют изменения или откатывают
Проблемы: медленно, требует поддержки БД, не всегда reliable.
Решение 2: Event Sourcing + Saga Pattern (лучше)
// 1. Запишем event
await eventLog.create({id: uuid(), type: 'transfer_initiated', from: user1, to: user2, amount: 100});
// 2. Обновим БД 1
await db1.users.update({id: user1, balance: balance - 100});
if (error) {
// Откатим event
await eventLog.update({id, status: 'failed'});
throw error;
}
// 3. Обновим БД 2
await db2.users.update({id: user2, balance: balance + 100});
if (error) {
// ОТКАТА: вернём деньги в БД 1
await db1.users.update({id: user1, balance: balance + 100});
await eventLog.update({id, status: 'failed'});
throw error;
}
// 4. Завершим
await eventLog.update({id, status: 'completed'});
Решение 3: Outbox Pattern (production-ready)
Проблема: что если процесс крашится между db1 и db2 update?
// Всё в одной БД транзакции:
await db1.transaction(async (trx) => {
// 1. Обновляем пользователя
await trx('users').where('id', user1).decrement('balance', 100);
// 2. Пишем event в outbox (same transaction!)
await trx('outbox').insert({
id: uuid(),
event_type: 'transfer_completed',
payload: {to: user2, amount: 100},
created_at: now()
});
});
// Отдельный процесс читает outbox и отправляет в db2:
setInterval(async () => {
const events = await db1('outbox').where('processed', false);
for (const event of events) {
try {
// Отправляем в db2
await db2.users.update({id: event.payload.to, balance: ...});
// Помечаем как processed
await db1('outbox').where('id', event.id).update({processed: true});
} catch (e) {
// Retry логика
}
}
}, 5000);
Решение 4: Message Queue (самое надёжное)
// В db1:
await db1.transaction(async (trx) => {
await trx('users').decrement('balance', 100);
// Пусти message в очередь (same transaction или after)
await queue.publish('transfer.completed', {user_id: user2, amount: 100});
});
// Consumer слушает очередь и обновляет db2:
queue.subscribe('transfer.completed', async (message) => {
try {
await db2.users.update({id: message.user_id, balance: ...});
// Acknowledge message
message.ack();
} catch (e) {
message.nack(); // Retry
}
});
Какой выбрать?
- 2PC: Старые legacy системы
- Event Sourcing: Когда нужна история всех операций
- Outbox: Хороший баланс
- Message Queue: Для микросервисов, максимальная надёжность
Практический совет: Часто лучше избежать distributed transactions перепроектировав архитектуру. Например, вместо двух БД - одна БД с хорошей архитектурой.