← Назад к вопросам
Может ли одна транзакция в базе данных включать несколько SQL-операций?
1.3 Junior🔥 271 комментариев
#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Транзакции: несколько SQL-операций
Краткий ответ: Да, одна транзакция может (и часто должна) содержать несколько SQL-операций. Это один из ключевых механизмов обеспечения целостности данных.
Основной концепт
Транзакция — это логическая единица работы, состоящая из одной или нескольких SQL-операций. Все операции в транзакции выполняются атомарно (либо все, либо ничего) благодаря принципам ACID:
@Service
public class BankTransferService {
@Autowired
private AccountRepository accountRepository;
@Transactional // Вся работа в одной транзакции
public void transferFunds(Long fromAccountId, Long toAccountId, BigDecimal amount) {
// Операция 1: дебет со счета
Account sender = accountRepository.findByIdForUpdate(fromAccountId);
sender.setBalance(sender.getBalance().subtract(amount));
accountRepository.save(sender); // SQL UPDATE #1
// Операция 2: кредит на счет
Account receiver = accountRepository.findByIdForUpdate(toAccountId);
receiver.setBalance(receiver.getBalance().add(amount));
accountRepository.save(receiver); // SQL UPDATE #2
// Если всё прошло успешно → COMMIT
// Если исключение → ROLLBACK (оба UPDATE отменяются)
}
}
Структура транзакции
-- BEGIN TRANSACTION
BEGIN; -- или START TRANSACTION
-- SQL операция 1
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- SQL операция 2
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- SQL операция 3
INSERT INTO transaction_history (from_id, to_id, amount) VALUES (1, 2, 100);
-- Если всё OK:
COMMIT; -- Все изменения сохраняются
-- Если ошибка:
ROLLBACK; -- Все изменения отменяются
Пример: Комплексная операция
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryRepository inventoryRepository;
@Autowired
private PaymentRepository paymentRepository;
@Transactional // Все SQL операции в одной транзакции
public Order createOrder(CreateOrderRequest request) {
// Операция 1: создание заказа
Order order = new Order();
order.setCustomerId(request.getCustomerId());
order.setTotalAmount(request.getTotalAmount());
order.setStatus(OrderStatus.CREATED);
orderRepository.save(order); // INSERT
// Операция 2: резервирование товара
Inventory inventory = inventoryRepository.findByProductIdForUpdate(request.getProductId());
if (inventory.getQuantity() < request.getQuantity()) {
throw new InsufficientInventoryException();
}
inventory.setQuantity(inventory.getQuantity() - request.getQuantity());
inventoryRepository.save(inventory); // UPDATE
// Операция 3: создание платежа
Payment payment = new Payment();
payment.setOrderId(order.getId());
payment.setAmount(request.getTotalAmount());
payment.setStatus(PaymentStatus.PENDING);
paymentRepository.save(payment); // INSERT
// Если всё прошло → COMMIT (все 3 SQL выполнены)
// Если исключение → ROLLBACK (все отменяются)
return order;
}
}
Управление транзакциями в Java
1. Spring @Transactional
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private AuditRepository auditRepository;
@Transactional
public User updateUser(Long userId, UpdateUserRequest request) {
// SQL операция 1
User user = userRepository.findById(userId).orElseThrow();
user.setName(request.getName());
user.setEmail(request.getEmail());
userRepository.save(user); // UPDATE
// SQL операция 2
AuditLog log = new AuditLog();
log.setUserId(userId);
log.setAction("UPDATE_PROFILE");
log.setChangedFields(String.join(",", getChangedFields(user, request)));
auditRepository.save(log); // INSERT
return user;
// COMMIT если успех, ROLLBACK если ошибка
}
}
2. Programmatic Transaction Management
@Service
public class ManualTransactionService {
@Autowired
private TransactionTemplate transactionTemplate;
public void complexOperation() {
transactionTemplate.executeWithoutResult(status -> {
// SQL операция 1
updateAccount(1, -100);
// SQL операция 2
updateAccount(2, +100);
// SQL операция 3
logTransaction(1, 2, 100);
// Автоматический COMMIT
// Если исключение → автоматический ROLLBACK
});
}
}
3. Raw SQL с явным управлением
public void manualTransactionControl(DataSource dataSource) {
Connection connection = null;
try {
connection = dataSource.getConnection();
// Начало транзакции
connection.setAutoCommit(false);
// SQL операция 1
String sql1 = "UPDATE accounts SET balance = balance - ? WHERE id = ?";
PreparedStatement stmt1 = connection.prepareStatement(sql1);
stmt1.setBigDecimal(1, new BigDecimal("100"));
stmt1.setLong(2, 1);
stmt1.executeUpdate();
// SQL операция 2
String sql2 = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
PreparedStatement stmt2 = connection.prepareStatement(sql2);
stmt2.setBigDecimal(1, new BigDecimal("100"));
stmt2.setLong(2, 2);
stmt2.executeUpdate();
// Коммит если успех
connection.commit();
} catch (SQLException e) {
if (connection != null) {
try {
// Откат если ошибка
connection.rollback();
} catch (SQLException rollbackException) {
e.addSuppressed(rollbackException);
}
}
throw new RuntimeException(e);
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// ...
}
}
}
}
Isolation Levels (уровни изоляции)
@Service
public class IsolationExampleService {
@Autowired
private OrderRepository orderRepository;
// READ_UNCOMMITTED: грязное чтение
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Order readDirtyData() {
return orderRepository.findById(1).orElseThrow();
}
// READ_COMMITTED: по умолчанию
@Transactional(isolation = Isolation.READ_COMMITTED)
public Order readCommittedData() {
return orderRepository.findById(1).orElseThrow();
}
// REPEATABLE_READ: повторяемое чтение
@Transactional(isolation = Isolation.REPEATABLE_READ)
public Order readRepeatableData() {
Order order = orderRepository.findById(1).orElseThrow();
// Если другой поток изменит order, мы это не увидим
Order order2 = orderRepository.findById(1).orElseThrow();
return order; // Гарантировано: order equals order2
}
// SERIALIZABLE: полная изоляция (может быть медленнее)
@Transactional(isolation = Isolation.SERIALIZABLE)
public Order serializableRead() {
return orderRepository.findById(1).orElseThrow();
}
}
Проблемы при несоблюдении транзакций
Проблема 1: Грязное состояние
// ❌ Плохо: без транзакции
public void transferFunds(Long from, Long to, BigDecimal amount) {
// Операция 1: дебет
debit(from, amount); // SQL UPDATE #1
// ЕСЛИ ЗДЕСЬ ОШИБКА:
int x = 1 / 0; // Exception!
// Операция 2 НИКОГДА НЕ ВЫПОЛНИТСЯ
credit(to, amount); // SQL UPDATE #2 — НЕ ВЫПОЛНЕН
// Результат: деньги исчезли! 🚨
}
// ✓ Хорошо: с транзакцией
@Transactional
public void transferFunds(Long from, Long to, BigDecimal amount) {
debit(from, amount);
int x = 1 / 0; // Exception!
credit(to, amount);
// Транзакция: ROLLBACK, оба изменения отменяются ✓
}
Проблема 2: Lost Update
// ❌ Без транзакции: гонка данных
Thread 1: читает balance = 100
Thread 2: читает balance = 100
Thread 1: пишет balance = 90 (вычел 10)
Thread 2: пишет balance = 80 (вычел 20)
// Результат: потеря 10 (должно быть 70)
// ✓ С SELECT FOR UPDATE:
@Transactional
public void updateBalance(Long accountId, BigDecimal delta) {
Account account = accountRepository.findByIdForUpdate(accountId);
// Блокируем строку, другие потоки ждут
account.setBalance(account.getBalance().add(delta));
accountRepository.save(account);
}
Лучшие практики
@Service
public class BestPractices {
@Autowired
private OrderRepository orderRepository;
// ✓ Правильно: минимальная область транзакции
@Transactional
public Order createOrder(CreateOrderRequest request) {
// Только БД операции
Order order = new Order();
order.setCustomerId(request.getCustomerId());
return orderRepository.save(order);
}
// ✗ Неправильно: слишком большая область
@Transactional
public void createOrderAndSendEmail(CreateOrderRequest request) {
// БД операции
Order order = new Order();
orderRepository.save(order);
// Долгая операция (отправка email)
emailService.sendConfirmation(order); // Плохо: держит транзакцию открытой
}
// ✓ Правильно: разделение ответственности
@Transactional
public Order createOrder(CreateOrderRequest request) {
Order order = new Order();
return orderRepository.save(order);
}
public void handleOrderCreated(Order order) {
// Вне транзакции, асинхронно
emailService.sendConfirmation(order);
}
}
Вывод: Да, одна транзакция может (и должна) содержать несколько SQL-операций для обеспечения целостности данных.