Что такое транзакция в базах данных и как она реализуется в Node.js?

Ответ

Транзакция — это атомарная последовательность операций с базой данных, которая либо выполняется полностью (commit), либо полностью откатывается (rollback) при ошибке. В Node.js работа с транзакциями зависит от используемой базы данных и драйвера/ORM.

Реализация транзакций с PostgreSQL через node-postgres:

const { Client } = require('pg');
const client = new Client();
await client.connect();

// Начало транзакции
await client.query('BEGIN');

try {
  // Операция 1: Создание пользователя
  await client.query(
    'INSERT INTO users(name, email) VALUES($1, $2) RETURNING id',
    ['John Doe', 'john@example.com']
  );

  // Операция 2: Создание заказа для пользователя
  const result = await client.query(
    'INSERT INTO orders(user_id, amount) VALUES($1, $2) RETURNING id',
    [1, 100.50]
  );

  // Фиксация транзакции
  await client.query('COMMIT');
  console.log('Transaction committed, order ID:', result.rows[0].id);
} catch (error) {
  // Откат при любой ошибке
  await client.query('ROLLBACK');
  console.error('Transaction rolled back:', error.message);
  throw error;
} finally {
  await client.end();
}

Реализация через Sequelize ORM:

const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password');

const transaction = await sequelize.transaction({
  isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED
});

try {
  // Создание пользователя в транзакции
  const user = await User.create({
    name: 'Alice',
    email: 'alice@example.com'
  }, { transaction });

  // Создание связанной записи
  await Order.create({
    userId: user.id,
    total: 250.75,
    status: 'pending'
  }, { transaction });

  // Фиксация
  await transaction.commit();
} catch (error) {
  // Автоматический откат
  await transaction.rollback();
  console.error('Transaction failed:', error);
}

Свойства ACID в контексте Node.js:

  1. Атомарность (Atomicity):

    // Либо обе операции выполнятся, либо ни одной
    await transferFunds(senderId, receiverId, amount);
  2. Согласованность (Consistency):

    // Транзакция гарантирует соблюдение constraints
    // FOREIGN KEY, CHECK, NOT NULL и т.д.
  3. Изолированность (Isolation):

    // Уровни изоляции в PostgreSQL через Node.js
    const levels = {
     READ_UNCOMMITTED: 'READ UNCOMMITTED',
     READ_COMMITTED: 'READ COMMITTED',    // По умолчанию
     REPEATABLE_READ: 'REPEATABLE READ',
     SERIALIZABLE: 'SERIALIZABLE'
    };
  4. Долговечность (Durability):

    // После COMMIT данные гарантированно сохраняются
    // даже при сбое системы (WAL - Write Ahead Logging)

Практические сценарии использования в Node.js:

  • Финансовые операции (переводы между счетами)
  • Создание заказов с несколькими позициями
  • Обновление инвентаря при покупке
  • Миграции данных с сохранением целостности

Ответ 18+ 🔞

Слушай, давай разберем эту тему про транзакции, а то у некоторых от одного слова «ACID» уже волнение ебать начинается, как будто в химической лаборатории очутились.

Представь себе, что транзакция — это как поход в магазин за пивом и чипсами. Ты либо покупаешь всё вместе и идешь домой с полным пакетом (это COMMIT), либо, если на кассе карта ляжет или денег не хватит, ты нихуя не покупаешь и уходишь с пустыми руками (это ROLLBACK). Получить только пиво, а чипсы забыть — это не наш метод. Всё или ничего, атомарно, блядь.

Вот смотри, как это на коленке через node-postgres делается. Главное — начать и не забыть в конце прибраться, а то соединение висеть будет.

const { Client } = require('pg');
const client = new Client();
await client.connect();

// Начинаем наш маленький финансовый триллер
await client.query('BEGIN');

try {
  // Операция 1: Создание пользователя
  await client.query(
    'INSERT INTO users(name, email) VALUES($1, $2) RETURNING id',
    ['John Doe', 'john@example.com']
  );

  // Операция 2: Создание заказа для пользователя
  const result = await client.query(
    'INSERT INTO orders(user_id, amount) VALUES($1, $2) RETURNING id',
    [1, 100.50]
  );

  // Если дошли сюда без косяков — фиксируем, красавчики
  await client.query('COMMIT');
  console.log('Transaction committed, order ID:', result.rows[0].id);
} catch (error) {
  // А тут что-то пошло не так... Откатываем всё к хуям собачьим!
  await client.query('ROLLBACK');
  console.error('Transaction rolled back:', error.message);
  throw error;
} finally {
  // И в любом случае соединение закрываем, а то ресурсы жрёт
  await client.end();
}

А если ты любитель ORM и высоких абстракций, то в Sequelize это выглядит поприличнее, но суть та же — обёртка, которая за тебя BEGIN, COMMIT и ROLLBACK сама напишет.

const transaction = await sequelize.transaction({
  isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED
});

try {
  const user = await User.create({
    name: 'Alice',
    email: 'alice@example.com'
  }, { transaction });

  await Order.create({
    userId: user.id,
    total: 250.75,
    status: 'pending'
  }, { transaction });

  // Всё гуд, зафиксировали
  await transaction.commit();
} catch (error) {
  // Пиздец, ошибка — откатываем как будто ничего и не было
  await transaction.rollback();
  console.error('Transaction failed:', error);
}

Теперь про эти ваши ACID, а то без них никуда. Это, блядь, как четыре кита, на которых держится доверие к базе.

  1. Атомарность (Atomicity) — это про «пан или пропал». Как в том примере выше: либо юзер и заказ создались, либо нихуя. Никаких промежуточных состояний, где юзер есть, а заказа нет. Это пиздец как важно для денежных переводов, представляешь?

  2. Согласованность (Consistency) — база следит, чтобы ты не накосячил. Если есть внешние ключи, NOT NULL или проверки — транзакция либо пройдёт все эти правила, либо отвалится. Нельзя заказать 100500 единиц товара, которых на складе 10. База тебе: «Ну-ну, чувак, давай по-новой».

  3. Изолированность (Isolation) — вот тут веселье начинается. Представь, что два скрипта одновременно лезут в одни и те же данные. Чтобы они друг другу не насрали в тарелку, есть уровни изоляции. От «читай что хочешь, даже неподтверждённые данные» до полной паранойи SERIALIZABLE, где всё выполняется так, будто происходит строго по очереди, даже если работает овердохуища потоков. В Постгресе по умолчанию READ COMMITTED — нормальный такой, адекватный вариант.

  4. Долговечность (Durability) — это когда после COMMIT'а можешь хоть рубильник дернуть. Данные уже в надёжном журнале (WAL), и после перезагрузки они на месте будут. Не как в оперативке — чихнул, и всё пропало.

Где это всё в ноде юзать? Да везде, где важен порядок и целостность:

  • Перевод бабла со счёта на счёт. Списали у одного — зачислили другому. Иначе будет срака, а не финансы.
  • Оформление заказа в интернет-магазине: резервируем товары на складе, создаём запись заказа, списываем деньги. Если на любом этапе облом — откатываем всю цепочку, чтобы товар не потерялся в небытии.
  • Сложные миграции данных, когда нужно переложить кучу записей из одной таблицы в другую, обновив кучу связей.

Короче, транзакции — это твой главный инструмент против хаоса и бардака в данных. Без них писать что-то серьёзное — это как строить дом на песке во время урагана. Обязательно накроется медным тазом.

Видео-ответы