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

В чем выражается Rollback

1.8 Middle🔥 211 комментариев
#Базы данных и SQL

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

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

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

В чем выражается Rollback?

Rollback — это откат транзакции базы данных. При откате все изменения, которые были сделаны в рамках транзакции, отменяются и база возвращается в исходное состояние. Это критично для обеспечения ACID свойств.

Как работает Rollback

public class RollbackExample {
    private DataSource dataSource;
    
    public void transferMoney(int fromAccount, int toAccount, double amount) 
            throws SQLException {
        Connection conn = dataSource.getConnection();
        try {
            conn.setAutoCommit(false);  // начало транзакции
            
            // Шаг 1: снять деньги
            String updateFrom = "UPDATE accounts SET balance = balance - ? WHERE id = ?";
            PreparedStatement ps1 = conn.prepareStatement(updateFrom);
            ps1.setDouble(1, amount);
            ps1.setInt(2, fromAccount);
            ps1.executeUpdate();
            
            // Шаг 2: положить деньги
            String updateTo = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
            PreparedStatement ps2 = conn.prepareStatement(updateTo);
            ps2.setDouble(1, amount);
            ps2.setInt(2, toAccount);
            ps2.executeUpdate();
            
            // Если всё успешно
            conn.commit();  // фиксируем изменения
            
        } catch (SQLException e) {
            // При ошибке
            conn.rollback();  // откатываем оба UPDATE'а
            throw e;
        } finally {
            conn.close();
        }
    }
}

Выражение Rollback в базе данных

Пример: операция без Rollback (ПЛОХО)

-- Состояние до:
-- Account 1: balance = 1000
-- Account 2: balance = 500

BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;  -- balance = 900
UPDATE accounts SET balance = balance + 100 WHERE id = 2;  -- balance = 600
-- Ошибка! Network failure
COMMIT;  -- не выполнится

-- Что произошло: ROLLBACK был неявный
-- Account 1: balance = 1000 (откачено)
-- Account 2: balance = 500  (откачено)

Пример: явный Rollback (ПРАВИЛЬНО)

BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- Ошибка!
ROLLBACK;  -- явный откат

-- Результат: обе операции отменены
-- Account 1: balance = 1000
-- Account 2: balance = 500

Механизм Rollback в JDBC

public class JDBCRollback {
    public static void main(String[] args) throws SQLException {
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/db", "user", "pass");
        
        try {
            // Отключаем auto-commit для контролируемых транзакций
            conn.setAutoCommit(false);
            
            // Выполняем операции
            Statement stmt = conn.createStatement();
            stmt.execute("INSERT INTO users (name) VALUES ('John')");
            stmt.execute("INSERT INTO orders (user_id, amount) VALUES (1, 100)");
            
            // Все прошло успешно
            conn.commit();  // фиксируем обе INSERT'ы
            
        } catch (SQLException e) {
            // Ошибка - отменяем обе INSERT'ы
            conn.rollback();
            System.out.println("Transaction rolled back: " + e.getMessage());
        } finally {
            conn.close();
        }
    }
}

Rollback при исключениях

public class RollbackOnException {
    private UserService userService;
    private OrderService orderService;
    private DataSource dataSource;
    
    public void createUserWithOrder(User user, Order order) throws Exception {
        Connection conn = dataSource.getConnection();
        try {
            conn.setAutoCommit(false);
            
            userService.save(user, conn);  // INSERT user
            orderService.save(order, conn);  // INSERT order
            
            // Если orderService выбросил исключение
            // rollback отменит INSERT user
            
            conn.commit();
        } catch (Exception e) {
            conn.rollback();  // отменяем всё
            throw e;
        } finally {
            conn.close();
        }
    }
}

Rollback vs Commit

ОперацияРезультатПереход в режимИспользование
COMMITФиксирует все измененияНовая транзакцияПосле успешных операций
ROLLBACKОтменяет все измененияНовая транзакцияПри ошибке

Savepoint — частичный Rollback

public class SavepointExample {
    public static void main(String[] args) throws SQLException {
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/db", "user", "pass");
        
        try {
            conn.setAutoCommit(false);
            
            Statement stmt = conn.createStatement();
            stmt.execute("INSERT INTO accounts (id, name) VALUES (1, 'Alice')");
            stmt.execute("INSERT INTO accounts (id, name) VALUES (2, 'Bob')");
            
            // Создаем точку сохранения
            Savepoint sp1 = conn.setSavepoint("sp1");
            
            stmt.execute("INSERT INTO accounts (id, name) VALUES (3, 'Charlie')");
            stmt.execute("UPDATE accounts SET balance = 1000");  // Ошибка!
            
            // Откатываем только до sp1
            conn.rollback(sp1);
            
            // Теперь у нас есть Alice и Bob
            // Charlie откачена
            
            conn.commit();
            
        } catch (SQLException e) {
            conn.rollback();  // полный откат если что-то не так
        } finally {
            conn.close();
        }
    }
}

Spring Transactional Rollback

@Service
public class UserService {
    @Transactional  // Spring управляет транзакцией
    public void registerUser(User user) throws Exception {
        userRepository.save(user);
        // Если это выбросит исключение
        sendWelcomeEmail(user);
        // Spring автоматически вызовет rollback
        // пользователь не будет в БД
    }
    
    @Transactional(rollbackFor = PaymentException.class)
    public void processPayment(Payment payment) {
        paymentRepository.save(payment);
        
        try {
            chargeCard(payment);
        } catch (PaymentException e) {
            // Spring откатит транзакцию
            throw e;
        }
    }
}

Когда Rollback НЕ происходит

// ПЛОХО: Checked исключение — НЕ откатывает
@Transactional
public void processData() throws IOException {  // checked
    repository.save(data);
    readFile();  // IOException не откатит транзакцию!
}

// ПРАВИЛЬНО: RuntimeException откатывает
@Transactional
public void processData() {
    repository.save(data);
    if (someCondition) {
        throw new RuntimeException("Error");  // откатит
    }
}

// ПРАВИЛЬНО: явно указать rollbackFor
@Transactional(rollbackFor = IOException.class)
public void processData() throws IOException {
    repository.save(data);
    readFile();  // теперь откатит
}

Redo Log и Undo Log

Внутри БД откат работает так:

Redo Log (для восстановления после краша):
INSERT user 'John' at offset 1000
UPDATE account 1 SET balance=900 at offset 1005

Undo Log (для откката транзакции):
IF ROLLBACK: DELETE user 'John' from offset 1000
IF ROLLBACK: UPDATE account 1 SET balance=1000 from offset 1005

При ROLLBACK: выполняются операции из Undo Log в обратном порядке

Пример: Rollback при deadlock

public class DeadlockRollback {
    @Transactional(retry = 3)
    public void transfer(int from, int to, double amount) {
        // Thread 1:
        // UPDATE accounts SET balance=balance-100 WHERE id=1
        // UPDATE accounts SET balance=balance+100 WHERE id=2
        
        // Thread 2:
        // UPDATE accounts SET balance=balance+50 WHERE id=2
        // UPDATE accounts SET balance=balance-50 WHERE id=1
        
        // DEADLOCK! БД откатит одну из транзакций
        // Spring перезапустит метод (retry)
    }
}

Лучшие практики

// 1. Минимизируй размер транзакции
@Transactional
public void quickOperation() {
    repository.save(data);  // быстро
    // не делай долгие операции
}

// 2. Обрабатывай исключения явно
@Transactional
public void processWithError() {
    try {
        repository.save(data);
    } catch (DataException e) {
        log.error("Failed to save", e);
        throw new RuntimeException(e);  // откатит
    }
}

// 3. Используй read-only для чтения
@Transactional(readOnly = true)
public List<User> getAllUsers() {
    return repository.findAll();
}

// 4. Правильный уровень изоляции
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateBalance(int accountId, double amount) {
    // защита от грязного чтения
}

Вывод

Rollback выражается как:

  1. JDBC: conn.rollback() отменяет все UPDATE/INSERT/DELETE
  2. SQL: ROLLBACK команда откатывает транзакцию
  3. Spring: @Transactional автоматически откатывает на исключение
  4. БД: Undo log отменяет операции в обратном порядке

Рollback гарантирует что если транзакция не завершилась успешно — база остаётся в консистентном состоянии (A в ACID).