Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ: Транзакции - атомарные единицы работы с БД
Транзакция в SQL - это последовательность операций, которые либо все выполняются, либо все отменяются. Это гарантирует целостность данных.
Основные свойства: ACID
A - Atomicity (Атомарность)
Транзакция либо полностью завершится, либо полностью откатится:
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- Обе операции выполнены
-- или ROLLBACK; -- Обе отменены
Если произойдёт сбой между операциями, БД откатит обе.
C - Consistency (Согласованность)
Данные остаются в согласованном состоянии:
BEGIN TRANSACTION;
-- Сумма денег в системе остаётся неизменной
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- Убыток
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- Доход
-- Инвариант: сумма денег = 100000 + 100000 = 200000
COMMIT; -- Инвариант сохранён
I - Isolation (Изоляция)
Транзакции не видят незавершённые изменения друг друга:
Транзакция 1 Транзакция 2
BEGIN
UPDATE account SET x=100
BEGIN
SELECT x <- Видит 100 или старое значение?
(зависит от isolation level)
D - Durability (Долговечность)
Когда транзакция закоммичена, данные сохранены:
COMMIT; -- Написано на диск, даже если упадёт сервер
Уровни изоляции
1. Read Uncommitted (грязное чтение)
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- Транзакция 1 Транзакция 2
BEGIN; BEGIN;
UPDATE orders SET total = 100 WHERE id = 1;
SELECT * FROM orders WHERE id = 1; -- Видит 100!
(Может быть откачено)
ROLLBACK; -- Откатываем
-- Но Транзакция 2 уже видела 100!
Проблема: грязное чтение (dirty read)
2. Read Committed (без грязных чтений)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN; BEGIN;
UPDATE orders SET total = 100 WHERE id = 1;
SELECT * FROM orders WHERE id = 1;
-- Видит старое значение, не 100!
COMMIT; -- Теперь изменения видны
-- Теперь видит 100
COMMIT;
Проблема: non-repeatable read
3. Repeatable Read (повторяемое чтение)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN; BEGIN;
SELECT COUNT(*) FROM orders; -- 10
INSERT INTO orders VALUES (...); -- Вставляем заказ
COMMIT;
SELECT COUNT(*) FROM orders; -- 10!
(В пределах транзакции видит первоначальный снимок)
COMMIT;
Проблема: phantom read (фантомное чтение)
4. Serializable (сериализуемость)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Транзакции выполняются как если бы они были одна за другой
-- Самый безопасный, но медленный
Синтаксис транзакций
PostgreSQL
BEGIN; -- Начало транзакции
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
UPDATE users SET age = 30 WHERE name = 'Alice';
DELETE FROM users WHERE id > 1000;
COMMIT; -- Применить изменения
-- или
ROLLBACK; -- Отменить все
MySQL
START TRANSACTION;
-- или
BEGIN;
INSERT INTO products (name, price) VALUES ('Laptop', 1000);
UPDATE inventory SET quantity = quantity - 1;
COMMIT;
-- или
ROLLBACK;
Savepoints (точки сохранения)
BEGIN;
INSERT INTO log VALUES ('operation 1');
SAVEPOINT sp1; -- Запомнить текущее состояние
INSERT INTO log VALUES ('operation 2');
INSERT INTO log VALUES ('operation 3'); -- Ошибка!
ROLLBACK TO sp1; -- Откатиться до sp1, но не до начала транзакции
INSERT INTO log VALUES ('operation 2 (fixed)');
COMMIT; -- Применяем: operation 1 и operation 2 (fixed)
В C++
#include <pqxx/pqxx>
int main() {
pqxx::connection c("dbname=mydb");
// Явная транзакция
{
pqxx::work txn(c); // BEGIN
txn.exec("INSERT INTO accounts VALUES (1, 'Alice', 1000)");
txn.exec("INSERT INTO accounts VALUES (2, 'Bob', 1000)");
txn.commit(); // COMMIT
}
// Автоматический RAII: если вылетит исключение, автоматически ROLLBACK
try {
pqxx::work txn(c);
txn.exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
txn.exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
// Может быть исключение
check_result(); // Если выкинет, ROLLBACK автоматически
txn.commit();
} catch (const std::exception& e) {
std::cerr << "Transaction failed: " << e.what() << std::endl;
// Транзакция уже откачена
}
}
Deadlock (взаимная блокировка)
Транзакция A Транзакция B
LOCK table1
LOCK table2
WAIT for table2 WAIT for table1
↑ ↑
└──────────────────────┘
Deadlock!
Решение:
- Всегда блокируй таблицы в одинаковом порядке
- Используй timeouts
- Сделай БД умнее (она обычно автоматически откатывает одну транзакцию)
Производительность
-- ❌ Медленно: много маленьких транзакций
FOR i = 1 TO 1000000:
BEGIN;
INSERT INTO data VALUES (i);
COMMIT;
-- 1 миллион транзакций = 1 миллион операций диска!
-- ✅ Быстро: одна большая транзакция
BEGIN;
FOR i = 1 TO 1000000:
INSERT INTO data VALUES (i);
COMMIT;
-- Одна операция диска!
Проблема Lost Update
Транзакция A Транзакция B
READ balance=100
READ balance=100
WRITE balance=150
WRITE balance=120
Итог: balance=120 (потеряли +50)
Решение: SELECT FOR UPDATE
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- Блокируем
UPDATE accounts SET balance = balance + 50 WHERE id = 1;
COMMIT;
Лучшие практики
✅ Держи транзакции короткими - меньше блокировок ✅ Коммитьте часто - не держи блокировки долго ✅ Используй индексы - ускоряет поиск в трнзакции ✅ Обрабатывай исключения - откатывай при ошибке ✅ Логируй транзакции - для audit trail ✅ Используй FOR UPDATE - если критична целостность ⚠️ Избегай вложенных транзакций - БД их не поддерживает (используй savepoints) ⚠️ Помни про deadlock - можно выполнить дважды
Итог
✅ ACID - гарантии БД ✅ BEGIN/COMMIT/ROLLBACK - управление транзакциями ✅ Isolation levels - компромисс между безопасностью и производительностью ✅ Savepoints - точки отката внутри транзакции ✅ Выполняй скоро - длинные транзакции = низкая производительность