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

Какие знаешь свойства транзакций?

2.2 Middle🔥 191 комментариев
#Архитектура и паттерны#Базы данных (SQL)

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

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

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

ACID свойства транзакций в базах данных

Транзакция — это последовательность операций над базой данных, которая должна выполняться либо полностью, либо не выполняться вообще. Надёжность транзакций обеспечивают четыре принципа ACID.

1. Atomicity (Атомарность)

Транзакция либо полностью выполняется, либо полностью откатывается. Нет промежуточных состояний.

Пример — перевод денег между счётами:

# BEGIN TRANSACTION
# UPDATE accounts SET balance = balance - 100 WHERE id = 1;
# UPDATE accounts SET balance = balance + 100 WHERE id = 2;
# COMMIT;

Если во время второго UPDATE произойдёт сбой:

  • Или обе операции выполнены (деньги переведены)
  • Или обе откатаны (деньги не переведены)
  • Никогда не будет ситуации, когда у одного счёта минус 100, а у другого плюс 0

Реализация в Python (SQLAlchemy):

from sqlalchemy.orm import Session

try:
    session = Session(engine)
    
    # Все операции в одной транзакции
    account1 = session.query(Account).filter_by(id=1).with_for_update().one()
    account2 = session.query(Account).filter_by(id=2).with_for_update().one()
    
    account1.balance -= 100
    account2.balance += 100
    
    session.commit()  # Атомарно
except Exception as e:
    session.rollback()  # Откат
    raise

2. Consistency (Согласованность)

База данных переходит из одного согласованного состояния в другое. Все ограничения целостности (constraints) соблюдаются до и после транзакции.

Примеры ограничений:

  • PRIMARY KEY — уникальность ID
  • FOREIGN KEY — ссылки между таблицами корректны
  • CHECK — проверка условий (age > 0)
  • NOT NULL — обязательные поля
  • UNIQUE — уникальность значения
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    age INTEGER CHECK (age >= 18),
    department_id INTEGER REFERENCES departments(id)
);

-- Попытка вставить некорректные данные
INSERT INTO users (id, email, age) 
VALUES (1, 'user@example.com', 15);  -- ОШИБКА: age < 18

INSERT INTO users (id, email, age) 
VALUES (1, NULL, 25);  -- ОШИБКА: email NULL

INSERT INTO users (id, email, age) 
VALUES (1, 'test@example.com', 25);  -- OK

В Python:

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    email = Column(String(255), unique=True, nullable=False)
    age = Column(Integer, CheckConstraint('age >= 18'))
    department_id = Column(Integer, ForeignKey('departments.id'))

# При сохранении БД проверит все constraints
session.add(User(id=1, email='user@example.com', age=25))
session.commit()  # Гарантирует согласованность

3. Isolation (Изолированность)

Одновременные транзакции не должны мешать друг другу. Каждая видит согласованный снимок данных.

Проблемы без изоляции:

Dirty Read (грязное чтение)

Транзакция B видит незафиксированные изменения от транзакции A.

Трансакция A              Транзакция B
BEGIN                     
UPDATE balance = 100      
                          SELECT balance  <- видит 100 (ошибка!)
ROLLBACK                  
(баланс остался 50)       

Non-repeatable Read (нестабильное чтение)

Два чтения одного значения в одной транзакции дают разные результаты.

Трансакция A              Транзакция B
BEGIN                     
SELECT age = 25           
                          UPDATE age = 30
                          COMMIT
SELECT age = 30 (было 25!)  <- противоречие
COMMIT

Phantom Read (фантомное чтение)

Два чтения одного набора строк дают разные результаты (добавились или удалились строки).

Трансакция A              Транзакция B
BEGIN                     
SELECT COUNT(*) = 5       
                          INSERT new_row
                          COMMIT
SELECT COUNT(*) = 6 (было 5!)  <- фантом
COMMIT

Уровни изоляции (от низкого к высокому):

# SERIALIZABLE (самый строгий)
# Как будто транзакции выполняются последовательно
session.execute(text('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE'))

# REPEATABLE READ (PostgreSQL по умолчанию)
# Защита от Dirty Read и Non-repeatable Read
# Но возможны Phantom Read

# READ COMMITTED (MySQL по умолчанию)
# Защита только от Dirty Read
# Возможны Non-repeatable Read и Phantom Read

# READ UNCOMMITTED (небезопасно)
# Без защиты от любых проблем
# В SQLAlchemy:
from sqlalchemy import text

session.execute(text('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE'))

4. Durability (Долговечность)

Однажды подтверждённые данные сохраняются навсегда, даже при сбое системы.

Как это работает:

  1. Все операции перед COMMIT логируются на диск (Write-Ahead Log)
  2. Только после успешной записи в лог выполняется COMMIT
  3. При восстановлении после сбоя можно восстановить все подтверждённые транзакции
Процесс с Durability:
┌─────────────────┐
│ Application     │ Отправляет COMMIT
└────────┬────────┘
         │
┌────────▼────────┐
│ Write-Ahead Log │ Пишет в лог на диск
└────────┬────────┘
         │
┌────────▼────────┐
│ Main Database   │ Применяет изменения
└────────┬────────┘
         │
┌────────▼────────┐
│ COMMITTED ✓     │ Подтверждено
└─────────────────┘

Если сбой между Write-Ahead Log и Main Database,
при восстановлении система применит изменения из лога.
Если сбой после COMMITTED, данные уже на диске.

Пример транзакции с ACID

from sqlalchemy.orm import Session
from sqlalchemy import create_engine, text

engine = create_engine('postgresql://user:password@localhost/db')

def transfer_money(from_user_id: int, to_user_id: int, amount: float):
    """Безопасный перевод денег между счётами (ACID гарантирует)"""
    
    with Session(engine) as session:
        try:
            # ATOMICITY: либо обе операции, либо ничего
            from_account = session.query(Account).filter_by(id=from_user_id).with_for_update().one()
            to_account = session.query(Account).filter_by(id=to_user_id).with_for_update().one()
            
            # CONSISTENCY: проверка constraints
            if from_account.balance < amount:
                raise ValueError("Insufficient balance")
            
            # Выполняем операции
            from_account.balance -= amount
            to_account.balance += amount
            
            # Создаём транзакцию в audit log (CONSISTENCY)
            transaction = Transaction(
                from_user_id=from_user_id,
                to_user_id=to_user_id,
                amount=amount,
                status="completed"
            )
            session.add(transaction)
            
            # ISOLATION: другие транзакции не видят промежуточные состояния
            # DURABILITY: после commit данные безопасно на диске
            session.commit()
            
            return True
        
        except Exception as e:
            session.rollback()
            print(f"Transaction failed: {e}")
            return False

# Использование
transfer_money(from_user_id=1, to_user_id=2, amount=100)

Итог

СвойствоГарантирует
AtomicityЛибо всё, либо ничего
ConsistencyСоблюдение всех ограничений целостности
IsolationНезависимость одновременных транзакций
DurabilityПостоянность подтверждённых данных

ACID-транзакции критичны для любого приложения, где нужна надёжность: финансовые системы, системы управления заказами, учёт инвентаря.