Какие свойства ACID обеспечивают транзакции в базах данных и почему они важны?

«Какие свойства ACID обеспечивают транзакции в базах данных и почему они важны?» — вопрос из категории Базы данных, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

ACID-свойства:

  1. Атомарность (Atomicity): Транзакция выполняется как единое целое. Либо выполняются все ее операции, либо ни одна. При сбое (ошибка в коде, отключение питания) система выполняет откат (ROLLBACK) всех изменений.

    • Почему важно: Предотвращает частичное обновление данных (например, списание денег без их зачисления получателю).
  2. Согласованность (Consistency): Транзакция переводит базу данных из одного валидного состояния в другое. Ограничения целостности (UNIQUE, FOREIGN KEY, CHECK) не нарушаются.

    • Почему важно: Гарантирует, что бизнес-правила и логические инварианты данных всегда соблюдаются.
  3. Изолированность (Isolation): Параллельно выполняемые транзакции не влияют друг на друга. Их эффекты изолированы. Уровни изоляции (Read Committed, Repeatable Read, Serializable) определяют степень этой изоляции, балансируя между целостностью и производительностью.

    • Почему важно: Предотвращает аномалии параллельного доступа: грязное чтение, неповторяющееся чтение, фантомное чтение.
  4. Долговечность (Durability): После успешного завершения транзакции (COMMIT) ее изменения сохраняются в базе данных навсегда, даже в случае последующего сбоя системы.

    • Почему важно: Гарантирует, что подтвержденные данные (например, оплаченный заказ) не будут потеряны.

Пример на JDBC, демонстрирующий атомарность и откат:

Connection conn = dataSource.getConnection();
try {
    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(); // Списание со счета 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(); // Фиксация ТОЛЬКО если обе операции успешны
    System.out.println("Transfer completed.");

} catch (SQLException e) {
    // При ЛЮБОЙ ошибке откатываем обе операции
    if (conn != null) {
        conn.rollback();
        System.out.println("Transfer failed. Rollback executed.");
    }
} finally {
    if (conn != null) conn.close();
}

Без транзакций возможны катастрофические последствия: потеря данных, финансовые ошибки, нарушение целостности связей между таблицами.