Транзакции в SQL — BEGIN, COMMIT, ROLLBACK
Коротко
Транзакция — группа SQL-команд, которые выполняются как единое целое: либо все, либо ни одной. BEGIN открывает транзакцию, COMMIT подтверждает, ROLLBACK отменяет. Транзакции защищают данные от частичного обновления и ошибок. Аналитику транзакции нужны при INSERT/UPDATE/DELETE: проверить результат перед подтверждением. На собеседованиях спрашивают ACID, уровни изоляции и deadlock.
Базовый синтаксис
BEGIN; -- начало транзакции
UPDATE accounts
SET balance = balance - 1000
WHERE account_id = 1;
UPDATE accounts
SET balance = balance + 1000
WHERE account_id = 2;
COMMIT; -- подтвердить оба UPDATE
-- или
ROLLBACK; -- отменить оба UPDATEКлассический пример — перевод денег. Без транзакции: если первый UPDATE выполнился, а второй упал (сбой сети, ошибка), деньги списались, но не зачислились. С транзакцией: либо оба UPDATE применяются, либо оба отменяются.
ACID
Транзакции гарантируют четыре свойства — ACID:
| Свойство | Описание | Пример |
|---|---|---|
| Atomicity (атомарность) | Всё или ничего | Перевод: оба UPDATE или ни одного |
| Consistency (согласованность) | Данные остаются корректными | Сумма балансов не меняется |
| Isolation (изолированность) | Параллельные транзакции не мешают | Два перевода одновременно не теряют деньги |
| Durability (долговечность) | После COMMIT данные сохранены | Даже если сервер упадёт |
Практические примеры
Безопасный UPDATE
BEGIN;
-- Проверить, что затронет
SELECT COUNT(*) FROM users WHERE last_login < '2023-01-01';
-- 1847 строк
-- Выполнить обновление
UPDATE users
SET status = 'inactive'
WHERE last_login < '2023-01-01';
-- UPDATE 1847
-- Проверить результат
SELECT status, COUNT(*) FROM users GROUP BY status;
-- Если всё ок:
COMMIT;
-- Если что-то не так:
-- ROLLBACK;Паттерн «BEGIN → проверка → действие → проверка → COMMIT/ROLLBACK» — стандарт для любых массовых изменений данных.
Безопасный DELETE
BEGIN;
-- Сколько удалим?
SELECT COUNT(*) FROM orders WHERE order_date < '2020-01-01';
-- 12543
-- Удаляем
DELETE FROM orders WHERE order_date < '2020-01-01';
-- Проверяем
SELECT MIN(order_date) FROM orders;
-- 2020-01-01 — всё верно
COMMIT;SAVEPOINT — частичный откат
BEGIN;
INSERT INTO users (name, email) VALUES ('Иван', 'ivan@example.com');
SAVEPOINT sp1;
INSERT INTO users (name, email) VALUES ('Анна', 'INVALID_EMAIL');
-- Ошибка! Откатываем только до savepoint
ROLLBACK TO sp1;
-- Иван всё ещё в транзакции
INSERT INTO users (name, email) VALUES ('Анна', 'anna@example.com');
COMMIT;
-- Оба пользователя сохраненыSAVEPOINT создаёт точку сохранения внутри транзакции. ROLLBACK TO откатывает до этой точки, не отменяя всю транзакцию.
Уровни изоляции
Когда несколько транзакций работают одновременно, возникают проблемы:
| Проблема | Описание |
|---|---|
| Dirty read | Чтение незакоммиченных данных другой транзакции |
| Non-repeatable read | Повторное чтение даёт другой результат |
| Phantom read | Появляются новые строки при повторном SELECT |
Уровни изоляции от слабого к сильному:
| Уровень | Dirty read | Non-repeatable | Phantom |
|---|---|---|---|
| READ UNCOMMITTED | Да | Да | Да |
| READ COMMITTED | Нет | Да | Да |
| REPEATABLE READ | Нет | Нет | Да |
| SERIALIZABLE | Нет | Нет | Нет |
-- Установить уровень изоляции (PostgreSQL)
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT SUM(balance) FROM accounts;
-- Другие транзакции не изменят видимые данные до COMMIT
COMMIT;PostgreSQL по умолчанию — READ COMMITTED. MySQL InnoDB — REPEATABLE READ.
Автокоммит
В большинстве СУБД каждый отдельный запрос — отдельная транзакция:
-- Это автоматически обёрнуто в BEGIN...COMMIT
UPDATE users SET name = 'Иван' WHERE user_id = 1;
-- Изменение применено сразу, ROLLBACK невозможенЯвный BEGIN отключает автокоммит до COMMIT или ROLLBACK.
Deadlock
Deadlock — взаимная блокировка двух транзакций:
Транзакция 1: блокирует строку A, ждёт строку B
Транзакция 2: блокирует строку B, ждёт строку A
→ Обе ждут бесконечноСУБД обнаруживает deadlock и откатывает одну из транзакций. Защита: всегда блокировать строки в одинаковом порядке (например, по возрастанию ID).
Транзакции для аналитика
Аналитик обычно не пишет сложные транзакции, но использует их для:
- Безопасные UPDATE/DELETE — проверить результат перед COMMIT
- Скрипты миграции — несколько ALTER TABLE / INSERT в одной транзакции
- Заполнение витрин — DROP + CREATE + INSERT как атомарная операция
- Понимание на собеседовании — ACID и изоляция спрашивают часто
-- Пересоздание витрины атомарно
BEGIN;
DROP TABLE IF EXISTS daily_metrics;
CREATE TABLE daily_metrics AS
SELECT
DATE(created_at) AS dt,
COUNT(DISTINCT user_id) AS dau,
SUM(amount) AS revenue
FROM events
GROUP BY DATE(created_at);
COMMIT;Типичные ошибки
Забыли COMMIT. Транзакция без COMMIT блокирует строки. Другие запросы будут ждать. Всегда завершайте BEGIN явным COMMIT или ROLLBACK.
Слишком длинная транзакция. BEGIN → сложный SELECT на 10 минут → UPDATE → COMMIT. Всё это время строки заблокированы. Держите транзакции короткими.
ROLLBACK после COMMIT. Невозможно. COMMIT — необратим. Единственный способ «отменить» — написать обратный UPDATE/DELETE.
Вопросы с собеседований
-- Что такое ACID? -- Atomicity (всё или ничего), Consistency (данные корректны), Isolation (транзакции не мешают друг другу), Durability (после COMMIT данные сохранены навсегда).
-- Чем COMMIT отличается от ROLLBACK? -- COMMIT подтверждает все изменения транзакции. ROLLBACK отменяет все изменения. После COMMIT откатить нельзя.
-- Что такое deadlock? -- Взаимная блокировка: транзакция A ждёт ресурс транзакции B, а B ждёт ресурс A. СУБД обнаруживает и откатывает одну из них.
-- Какой уровень изоляции по умолчанию в PostgreSQL? -- READ COMMITTED. Каждый SELECT видит только закоммиченные данные, но повторный SELECT может дать другой результат (non-repeatable read).
Потренируйтесь решать задачи — откройте тренажёр с 1500+ вопросами для подготовки к собеседованиям аналитиков.
FAQ
Нужно ли аналитику знать транзакции глубоко?
Базово — да: BEGIN/COMMIT/ROLLBACK для безопасных UPDATE/DELETE и понимание ACID. Уровни изоляции и deadlock — для собеседований и понимания, почему запрос «зависает».
Транзакции в ClickHouse?
ClickHouse не поддерживает классические транзакции. Он оптимизирован для аналитических запросов (OLAP), не для транзакционной нагрузки (OLTP). Для транзакций — PostgreSQL, MySQL.
Как откатить COMMIT?
Никак. COMMIT — необратим. Можно только написать обратную операцию (UPDATE обратно, INSERT → DELETE). Поэтому всегда проверяйте результат перед COMMIT.
Как тренироваться
Транзакции и ACID — обязательная тема на SQL-собеседованиях. Задачи по SQL — в тренажёре Карьерник. Больше вопросов — в разделе с примерами.