Что такое транзакция в базах данных?

Ответ

Транзакция — это логическая единица работы с базой данных, которая состоит из одной или нескольких операций. Она выполняется по принципу "всё или ничего": либо все изменения фиксируются, либо все откатываются, что гарантирует целостность данных.

Свойства ACID:

  1. Atomicity (Атомарность): Транзакция выполняется полностью или не выполняется вовсе.
  2. Consistency (Согласованность): Транзакция переводит базу данных из одного валидного состояния в другое.
  3. Isolation (Изолированность): Параллельные транзакции не влияют друг на друга. Уровни изоляции (READ_COMMITTED, REPEATABLE_READ и др.) определяют степень этого влияния.
  4. Durability (Долговечность): После подтверждения (commit) изменения сохраняются даже при сбое системы.

Практический пример на JDBC:

Connection conn = dataSource.getConnection();
try {
    conn.setAutoCommit(false); // Начало транзакции (отключение auto-commit)

    PreparedStatement stmt1 = conn.prepareStatement(
        "UPDATE accounts SET balance = balance - ? WHERE id = ?");
    stmt1.setBigDecimal(1, new BigDecimal("100.00"));
    stmt1.setInt(2, 1);
    stmt1.executeUpdate(); // Списание со счета 1

    PreparedStatement stmt2 = conn.prepareStatement(
        "UPDATE accounts SET balance = balance + ? WHERE id = ?");
    stmt2.setBigDecimal(1, new BigDecimal("100.00"));
    stmt2.setInt(2, 2);
    stmt2.executeUpdate(); // Зачисление на счет 2

    conn.commit(); // Фиксация всех изменений
} catch (SQLException e) {
    conn.rollback(); // Откат всех изменений при ошибке
    throw e;
} finally {
    conn.setAutoCommit(true); // Восстановление режима по умолчанию
    conn.close();
}

Ответ 18+ 🔞

Слушай, ну вот объясняю тебе про транзакции, как будто ты в баре сидишь и пытаешься деньги другу перевести, а у тебя уже пятый бокал. Представь, что база данных — это твой кошелёк и кошелёк твоего кореша. Транзакция — это когда ты говоришь: «Так, ща я ему сотку перекину». И это всё или нихуя. Либо ты ему отправил и у тебя списалось, а у него прибавилось, либо, если что-то пошло не так (интернет сдох, ты опечатался), то всё как было. Никаких промежуточных состояний, типа «у тебя списалось, а он нихуя не получил». Это и есть атомарность, ёпта.

А теперь про эти ваши ACID, блядь. Запоминай, как мантру, а то потом будешь в коде жопу рвать.

  1. Атомарность (Atomicity) — это как раз про «всё или ничего». Как в том анекдоте: «Или грудь в крестиках, или жопа в бинтах». Третьего не дано.
  2. Согласованность (Consistency) — ну, тут всё логично. База не должна превратиться в сюрреалистичный пиздец после твоих манипуляций. Если у тебя в таблице accounts есть ограничение, что баланс не может быть отрицательным, то транзакция, которая попытается уйти в минус, просто отъедет нахер с ошибкой. База останется в валидном состоянии, в рот меня чих-пых.
  3. Изолированность (Isolation) — это, блядь, самое интересное. Представь, что вы с другом одновременно лезете в один холодильник за последним пивом. Кто первый схватит, тот и выпил. В базах та же хуйня: если две транзакции лезут в одни и те же данные, могут быть гонки. Поэтому придумали уровни изоляции — READ_COMMITTED, REPEATABLE_READ и прочие. Это как правила: «Сначала посмотри, свободен ли холодильник, а потом уже лезь». Чем строже уровень, тем меньше аномалий, но тем всё медленнее работает, потому что все друг другу мешают. Пиздец, короче.
  4. Долговечность (Durability) — это когда ты уже подтвердил перевод (commit), и даже если у тебя сервер хуяк и упал, после перезагрузки эти деньги всё равно будут переведены. Они как будто в каменную скрижаль записаны, а не на салфетке в баре.

А вот, смотри, как это выглядит в коде на JDBC. Это ж как инструкция по выживанию, блядь:

Connection conn = dataSource.getConnection();
try {
    // Выключаем эту хуйню auto-commit. Теперь мы сами рулим.
    conn.setAutoCommit(false);

    // Готовим первый удар: списываем сотку с первого счёта.
    PreparedStatement stmt1 = conn.prepareStatement(
        "UPDATE accounts SET balance = balance - ? WHERE id = ?");
    stmt1.setBigDecimal(1, new BigDecimal("100.00"));
    stmt1.setInt(2, 1);
    stmt1.executeUpdate(); // Пшшш, деньги полетели... но пока только в буфере.

    // Второй удар: зачисляем на второй счёт.
    PreparedStatement stmt2 = conn.prepareStatement(
        "UPDATE accounts SET balance = balance + ? WHERE id = ?");
    stmt2.setBigDecimal(1, new BigDecimal("100.00"));
    stmt2.setInt(2, 2);
    stmt2.executeUpdate(); // И эти тоже в буфере.

    // Всё прошло гладко? Отлично, хуяк — commit! Теперь изменения реально записались.
    conn.commit();
} catch (SQLException e) {
    // Ага, что-то пошло не так? Может, на втором счету ID не найден?
    // Тогда откатываем ВСЁ, что наделали в этой транзакции. И списание, и зачисление. Нахуй.
    conn.rollback();
    throw e; // И кидаем ошибку дальше, пусть разбираются.
} finally {
    // Обязательно включаем auto-commit обратно, чтобы не поймать сюрприз в другом месте.
    conn.setAutoCommit(true);
    conn.close(); // И закрываем соединение, а то ресурсы кончатся.
}

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