← Назад к вопросам
Как внутренняя транзакция выглядит в консоли БД
2.3 Middle🔥 111 комментариев
#ORM и Hibernate#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как выглядит транзакция в консоли БД
Что видно в логах БД
Внутренние транзакции выглядят в консоли БД последовательностью SQL команд в рамках одной сессии. Покажу конкретные примеры для PostgreSQL.
PostgreSQL: Явное управление транзакциями
Пример 1: Простая транзакция
-- Начало транзакции (явное)
BEGIN;
-- Операция 1: INSERT
INSERT INTO users (id, username, email, created_at)
VALUES (1, 'john_doe', 'john@example.com', now());
-- Операция 2: UPDATE
UPDATE accounts SET balance = balance - 100
WHERE user_id = 1;
-- Операция 3: INSERT
INSERT INTO transactions (user_id, amount, type)
VALUES (1, 100, 'withdrawal');
-- Завершение: фиксируем все изменения
COMMIT;
-- или откатываем (если ошибка)
ROLLBACK;
Как это выглядит в логе PostgreSQL
2024-03-22 14:35:22.123 [1234] LOG: statement: BEGIN;
2024-03-22 14:35:22.124 [1234] LOG: statement: INSERT INTO users (id, username, email, created_at) VALUES (1, 'john_doe', 'john@example.com', now());
2024-03-22 14:35:22.125 [1234] LOG: statement: UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
2024-03-22 14:35:22.126 [1234] LOG: statement: INSERT INTO transactions (user_id, amount, type) VALUES (1, 100, 'withdrawal');
2024-03-22 14:35:22.127 [1234] LOG: statement: COMMIT;
2024-03-22 14:35:22.128 [1234] LOG: statement: SELECT 1; -- next query
При ошибке (ROLLBACK)
2024-03-22 14:35:22.123 [1234] LOG: statement: BEGIN;
2024-03-22 14:35:22.124 [1234] LOG: statement: INSERT INTO users (id, username, email, created_at) VALUES (1, 'john_doe', 'john@example.com', now());
2024-03-22 14:35:22.125 [1234] ERROR: duplicate key value violates unique constraint "users_email_key"
2024-03-22 14:35:22.126 [1234] LOG: statement: ROLLBACK; -- всё откатывается
Spring Boot + Spring Data JPA: Как это работает
Java код
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private PaymentRepository paymentRepository;
@Transactional // Аннотация для управления транзакциями
public void createOrder(Order order, Payment payment) {
// 1. Сохраняем заказ
orderRepository.save(order);
// 2. Обрабатываем платёж
paymentRepository.save(payment);
// 3. Обновляем статус
order.setStatus("CONFIRMED");
orderRepository.save(order);
// В конце автоматически вызывается COMMIT
}
@Transactional
public void failingOrder(Order order) {
orderRepository.save(order);
// Если здесь выбросится исключение — всё откатится
throw new RuntimeException("Payment failed");
// ROLLBACK произойдёт автоматически
}
}
В логах БД появится
2024-03-22 14:35:22.100 [conn-5234] LOG: statement: BEGIN;
2024-03-22 14:35:22.101 [conn-5234] LOG: statement: INSERT INTO orders (id, user_id, total, status, created_at) VALUES (100, 42, 299.99, 'PENDING', now());
2024-03-22 14:35:22.102 [conn-5234] LOG: statement: INSERT INTO payments (id, order_id, amount, method) VALUES (1, 100, 299.99, 'CREDIT_CARD');
2024-03-22 14:35:22.103 [conn-5234] LOG: statement: UPDATE orders SET status = 'CONFIRMED' WHERE id = 100;
2024-03-22 14:35:22.104 [conn-5234] LOG: statement: COMMIT;
Пример: Nested Transactions
Java код с вложенными транзакциями
@Service
public class ComplexOrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
@Transactional
public void processOrder(Order order) {
// Основная транзакция начинается
orderRepository.save(order);
// Вызываем метод с @Transactional(propagation = REQUIRES_NEW)
try {
inventoryService.deductInventory(order.getItems());
} catch (InsufficientInventoryException e) {
// Вложенная транзакция откатилась
// Но основная может продолжить
order.setStatus("BACKORDER");
orderRepository.save(order);
}
// Основная транзакция коммитится
}
}
@Service
public class InventoryService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deductInventory(List<OrderItem> items) {
// Новая, отдельная транзакция
// COMMIT или ROLLBACK независимо от основной
}
}
В логах БД
2024-03-22 14:35:22.100 [conn-5234] LOG: statement: BEGIN; -- основная транзакция
2024-03-22 14:35:22.101 [conn-5234] LOG: statement: INSERT INTO orders ...;
2024-03-22 14:35:22.102 [conn-5234] LOG: statement: SAVEPOINT sp_1; -- SavePoint для вложения
2024-03-22 14:35:22.103 [conn-5234] LOG: statement: UPDATE inventory SET quantity = quantity - 5 WHERE product_id = 1;
2024-03-22 14:35:22.104 [conn-5234] LOG: statement: ROLLBACK TO sp_1; -- откатываем только вложенную часть
2024-03-22 14:35:22.105 [conn-5234] LOG: statement: UPDATE orders SET status = 'BACKORDER' ...;
2024-03-22 14:35:22.106 [conn-5234] LOG: statement: COMMIT; -- основная транзакция коммитится
MySQL: Аналогичный пример
-- Начало транзакции
START TRANSACTION;
-- Операции
INSERT INTO users ...
UPDATE accounts ...
DELETE FROM temp_data ...
-- Фиксируем
COMMIT;
Как включить логирование транзакций в Spring Boot
application.properties
# Логирование SQL
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
# Для PostgreSQL в логах
logging.level.org.postgresql.core=DEBUG
logging.level.org.postgresql.jdbc.PgConnection=DEBUG
# Для Hibernate
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
Вывод в консоль
Hibernate: BEGIN
Hibernate: INSERT INTO orders (id, total, status) VALUES (?, ?, ?)
Hibernate: INSERT INTO payments (id, order_id, amount) VALUES (?, ?, ?)
Hibernate: COMMIT
Isolation Levels и их отражение
Установка уровня изоляции
@Transactional(isolation = Isolation.READ_COMMITTED)
public void saveWithReadCommitted(Order order) {
// В логах БД появится:
// SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
orderRepository.save(order);
}
В консоли БД
2024-03-22 14:35:22.100 LOG: SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
2024-03-22 14:35:22.101 LOG: BEGIN;
2024-03-22 14:35:22.102 LOG: INSERT INTO orders ...;
2024-03-22 14:35:22.103 LOG: COMMIT;
Практический пример: Откат при ошибке
Java
@Service
public class TransferService {
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
Account from = accountRepository.findById(fromAccountId).orElseThrow();
Account to = accountRepository.findById(toAccountId).orElseThrow();
// Если баланс недостаточен — выбросится исключение
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException();
}
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
accountRepository.save(from);
accountRepository.save(to);
}
}
В логах БД (успех)
BEGIN;
UPDATE accounts SET balance = 900 WHERE id = 1;
UPDATE accounts SET balance = 1100 WHERE id = 2;
COMMIT;
В логах БД (ошибка и откат)
BEGIN;
UPDATE accounts SET balance = 900 WHERE id = 1;
-- Исключение в Java: InsufficientBalanceException
ROLLBACK;
-- Балансы остаются неизменными!
Итоговая схема
Spring @Transactional
↓
Spring Data JPA
↓
Hibernate ORM
↓
Database Driver (JDBC)
↓
Датабаза (PostgreSQL/MySQL)
↓
В логах БД видны: BEGIN, SQL операции, COMMIT/ROLLBACK
Ключевые моменты
- Группировка операций — всё между BEGIN и COMMIT выполняется атомарно
- Откат при ошибке — автоматический ROLLBACK при исключении
- Изоляция — транзакции не видят грязные данные друг друга
- Дурабельность — после COMMIT данные сохранены окончательно
- Вложенность — SAVEPOINT для вложенных транзакций