← Назад к вопросам

Сталкивался ли с транзакциями

1.3 Junior🔥 241 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Опыт с транзакциями в Node.js

Да, регулярно работаю с транзакциями в Node.js приложениях. Это критично для обеспечения консистентности данных при сложных операциях.

Реальные сценарии использования

1. Переводы денег между счётами

Это классический пример, где нужна ACID гарантия:

await prisma.$transaction(async (tx) => {
  // Уменьшаем баланс отправителя
  await tx.account.update({
    where: { id: senderId },
    data: { balance: { decrement: amount } },
  });
  
  // Увеличиваем баланс получателя
  await tx.account.update({
    where: { id: recipientId },
    data: { balance: { increment: amount } },
  });
  
  // Записываем транзакцию в лог
  await tx.transaction.create({
    data: {
      senderId,
      recipientId,
      amount,
      status: 'completed',
    },
  });
});

Если произойдёт ошибка в середине — всё откатится (rollback), и деньги не исчезнут.

2. Создание заказа с товарами

Нужно атомарно создать заказ и резервировать товары:

await prisma.$transaction(async (tx) => {
  // Создаём заказ
  const order = await tx.order.create({
    data: {
      userId,
      totalPrice: totalAmount,
    },
  });
  
  // Резервируем товары
  for (const item of items) {
    await tx.inventory.update({
      where: { productId: item.productId },
      data: { reserved: { increment: item.quantity } },
    });
    
    // Добавляем товары в заказ
    await tx.orderItem.create({
      data: {
        orderId: order.id,
        productId: item.productId,
        quantity: item.quantity,
      },
    });
  }
  
  return order;
});

3. Работа с raw SQL транзакциями

const result = await prisma.$transaction(async (tx) => {
  // Используем raw SQL в транзакции
  const [user] = await tx.$queryRaw`
    SELECT * FROM users WHERE id = ${userId} FOR UPDATE
  `;
  
  if (user.balance < amount) {
    throw new Error('Insufficient balance');
  }
  
  await tx.$executeRaw`
    UPDATE users SET balance = balance - ${amount} WHERE id = ${userId}
  `;
  
  await tx.$executeRaw`
    INSERT INTO transactions (user_id, amount) VALUES (${userId}, ${amount})
  `;
  
  return { success: true };
});

Важные концепции, которые узнал

1. ACID свойства

  • A (Atomicity) — либо всё, либо ничего. Нет половинок.
  • C (Consistency) — данные остаются в корректном состоянии
  • I (Isolation) — параллельные транзакции не мешают друг другу
  • D (Durability) — после commit данные на диске

2. Уровни изоляции

// Разные БД поддерживают разные уровни
// PostgreSQL:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

// READ UNCOMMITTED — самый слабый
// READ COMMITTED — дефолт в большинстве
// REPEATABLE READ
// SERIALIZABLE — самый сильный, но медленнее

3. SELECT FOR UPDATE (пессимистичная блокировка)

Когда нужна гарантия, что никто не изменит строку, пока мы работаем:

await prisma.$transaction(async (tx) => {
  const [account] = await tx.$queryRaw`
    SELECT * FROM accounts WHERE id = ${accountId} FOR UPDATE
  `;
  
  // Теперь никто не может изменить эту строку
  // пока не завершится транзакция
  
  await tx.account.update({
    where: { id: accountId },
    data: { balance: account.balance - amount },
  });
});

4. Обработка race conditions

try {
  await prisma.$transaction(async (tx) => {
    const product = await tx.product.findUnique({
      where: { id: productId },
    });
    
    if (product.stock < quantity) {
      throw new Error('Out of stock');
    }
    
    await tx.product.update({
      where: { id: productId },
      data: { stock: { decrement: quantity } },
    });
  });
} catch (error) {
  // Транзакция откатилась
  console.error('Transaction failed:', error);
}

Ошибки, которые делал и исправил

❌ Ошибка: Слишком долгие транзакции

// Плохо — транзакция ждёт внешнего API
await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({ ... });
  const response = await fetchExternalAPI(); // 5 сек ожидания!
  await tx.user.update({ ... });
});

✅ Исправление: Внешние операции вне транзакции

// Хорошо
const user = await prisma.user.create({ ... });

try {
  const response = await fetchExternalAPI();
  
  await prisma.$transaction(async (tx) => {
    await tx.user.update({ ... });
  });
} catch (error) {
  // Откатываем создание юзера при необходимости
  await prisma.user.delete({ where: { id: user.id } });
}

❌ Ошибка: Вложенные транзакции (Savepoints)

// Не все БД хорошо поддерживают
await prisma.$transaction(async (tx1) => {
  await tx1.user.create({ ... });
  
  await prisma.$transaction(async (tx2) => { // Может не работать!
    await tx2.post.create({ ... });
  });
});

Когда использовать транзакции

✅ Деньги и платежи
✅ Запасы/инвентарь
✅ Сложные бизнес-операции с несколькими шагами
✅ Когда нужна гарантия консистентности

Когда транзакции не нужны

❌ Простые CREATE/UPDATE операции
❌ Чтение данных (SELECT)
❌ Когда можно использовать eventual consistency

Best Practices

  1. Держи транзакции короткими — меньше времени, меньше блокировок
  2. Избегай вложенности — сложность растёт экспоненциально
  3. Обрабатывай ошибки — транзакция может откатиться неожиданно
  4. Логируй попытки и откаты — помогает отладке
  5. Мониторь deadlock'и — когда две транзакции ждут друг друга
  6. Тестируй сценарии отката — убедись, что откат работает

Инструменты для отладки

-- Вижу текущие транзакции в PostgreSQL
SELECT * FROM pg_stat_activity;

-- Проверяю блокировки
SELECT * FROM pg_locks;

-- Убиваю зависшую транзакцию
SELECT pg_terminate_backend(pid);

Вывод

Транзакции — это не опционально, это основа надёжности приложений, работающих с данными. Я постоянно использую их при разработке платёжных систем, работе с инвентарём и сложными бизнес-операциями. Главное — понимать, когда их использовать и как правильно их структурировать.

Сталкивался ли с транзакциями | PrepBro