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

Как распределялась нагрузка?

1.0 Junior🔥 181 комментариев
#Опыт работы и проекты

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

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

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

Ответ: Транзакции - атомарные единицы работы с БД

Транзакция в 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 - точки отката внутри транзакции ✅ Выполняй скоро - длинные транзакции = низкая производительность