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

Что такое сниппет (snippet)?

3.0 Senior🔥 61 комментариев
#Архитектура и паттерны#Асинхронность и многопоточность

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

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

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

Снимок (Snapshot) в БД

Snapshot (снимок) в базе данных — это консистентный моментальный снимок состояния данных в определённый момент времени. Snapshot позволяет читать данные без блокировок, обеспечивая изолированность транзакций. Каждая транзакция видит данные такими, какие они были в момент начала транзакции, независимо от изменений, сделанных другими транзакциями.

Основные концепции

Snapshot Isolation (SI) — уровень изолированности транзакций в современных СУБД. Вместо полной блокировки данных, система хранит версии данных и каждая транзакция работает с консистентным снимком данных.

Как работают snapshots

1. Версионирование данных (MVCC — Multi-Version Concurrency Control):

Время    Версия строки user_id=1
--|--------
T0      name = 'Иван', balance = 1000
T1      name = 'Иван', balance = 900  (обновление)
T2      name = 'Иван', balance = 850  (ещё обновление)
T3      name = 'Иван', balance = 800  (и ещё)

Транзакция начата в T1.5:
- Видит версию от T1: balance = 900
- Не видит версию от T2 и T3

Практический пример на PostgreSQL

1. Изолированность транзакций через snapshots:

-- Сессия 1: Начнём транзакцию
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT * FROM users WHERE id = 1;  -- Видит текущий снимок
-- Результат: balance = 1000

-- Сессия 2 (параллельно): Обновляем данные
BEGIN;
UPDATE users SET balance = 900 WHERE id = 1;
COMMIT;

-- Сессия 1: Повторяем запрос в одной транзакции
SELECT * FROM users WHERE id = 1;  -- ВСЁ ЕЩЁ видит старый снимок!
-- Результат: balance = 1000 (не изменилось!)

COMMIT;  -- Заканчиваем транзакцию сессии 1

2. Snapshot с явным указанием времени:

-- PostgreSQL с include_system_columns позволяет видеть версии
SELECT xmin, xmax, cmin, cmax, * FROM users;

-- xmin: ID транзакции, вставившей версию
-- xmax: ID транзакции, удалившей версию (0 если активна)
-- cmin/cmax: ID команды в транзакции

Уровни изолированности и snapshots

1. READ UNCOMMITTED — минимальная изолированность:

BEGIN ISOLATION LEVEL READ UNCOMMITTED;
SELECT * FROM users;  -- Видит even коммитнутые изменения
COMMIT;

2. READ COMMITTED — стандартный уровень:

BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT * FROM users;  -- Новый snapshot для каждого запроса
SELECT * FROM users;  -- Может видеть новые изменения
COMMIT;

3. REPEATABLE READ — снимок на уровне транзакции:

BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM users;  -- Снимок 1
SELECT * FROM users;  -- ТОТЖЕ снимок 1 (не видит новые изменения)
COMMIT;

4. SERIALIZABLE — строгая изолированность:

BEGIN ISOLATION LEVEL SERIALIZABLE;
-- Полная изолированность, как будто транзакции выполняются последовательно
COMMIT;

Snapshot в точке времени (Point-in-Time Recovery)

Использование snapshots для восстановления данных:

-- PostgreSQL: восстановить базу из backup на момент времени
-- В файле recovery.conf:
recovery_target_timeline = 'latest'
recovery_target_type = 'time'
recovery_target_time = '2024-01-15 14:30:00'

-- После этого БД восстанавливается в состояние на указанное время

Snapshot с использованием логов изменений

CDC (Change Data Capture) использует snapshots:

# Пример с Debezium (инструмент для CDC)
# Снимок используется для начального захвата данных

config = {
    "name": "postgres-cdc",
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.server.name": "my-app",
    "database.hostname": "localhost",
    "snapshot.mode": "always"  # Всегда делать снимок при запуске
}

Конфликты при snapshot изолированности

Phantom Read — появление новых строк в снимке:

-- Сессия 1
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT COUNT(*) FROM users WHERE status = 'active';
-- Результат: 100

-- Сессия 2
INSERT INTO users (status) VALUES ('active');
COMMIT;

-- Сессия 1: Повторяем запрос
SELECT COUNT(*) FROM users WHERE status = 'active';
-- Результат: ВСЕГДА 100 (не видит новую строку в snapshot)

COMMIT;

Serialization Conflict — конфликт при обновлении:

-- Сессия 1
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT balance FROM accounts WHERE id = 1;
-- balance = 1000

-- Сессия 2
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT balance FROM accounts WHERE id = 1;
UPDATE accounts SET balance = 900 WHERE id = 1;
COMMIT;

-- Сессия 1: Пытаемся обновить
UPDATE accounts SET balance = 950 WHERE id = 1;
-- ОШИБКА: Serialization conflict!
-- Транзакция отклонена, т.к. данные уже изменены после нашего снимка

COMMIT;

Управление snapshots в коде

Использование ORM (SQLAlchemy):

from sqlalchemy import create_engine, event
from sqlalchemy.orm import sessionmaker

engine = create_engine('postgresql://...')
Session = sessionmaker(bind=engine)

# Явно установить уровень изолированности
with engine.connect() as conn:
    conn.execution_options(isolation_level="SERIALIZABLE")
    # Все запросы в этом контексте используют снимок
    result = conn.execute("SELECT * FROM users")
    
    # Параллельные изменения не будут видны
    result = conn.execute("SELECT * FROM users")  # Тот же снимок

Explicit snapshot creation:

from datetime import datetime
from sqlalchemy import text

session = Session()

# Начать транзакцию со снимком
session.execute(text("BEGIN ISOLATION LEVEL REPEATABLE READ"))

try:
    # Все запросы видят консистентный снимок
    users = session.query(User).all()
    
    # Параллельные изменения не влияют
    for user in users:
        print(user.name)
    
    session.commit()
except Exception as e:
    session.rollback()
    print(f"Ошибка: {e}")

Управление жизненным циклом snapshots

Старые snapshots могут занимать место:

-- PostgreSQL: проверить возраст self snaphots
SELECT * FROM pg_stat_user_tables;

-- VACUUM очищает старые версии
VACUUM ANALYZE users;

-- Автоматический VACUUM
ALTER TABLE users SET (
    autovacuum = on,
    autovacuum_vacuum_scale_factor = 0.1,
    autovacuum_analyze_scale_factor = 0.05
);

Лучшие практики работы с snapshots

1. Используй правильный уровень изолированности:

# Для большинства случаев достаточно READ COMMITTED
session = Session(bind=engine.execution_options(
    isolation_level="READ_COMMITTED"
))

# Для критичных операций используй SERIALIZABLE
with session.begin(nested=True):
    # Автоматическое управление уровнем изолированности
    pass

2. Минимизируй время жизни транзакций:

# Плохо: длинная транзакция
with session.begin():
    user = session.query(User).first()
    time.sleep(10)  # Snapshot остаётся активным!
    user.name = "New Name"
    session.commit()

# Хорошо: короткая транзакция
user = session.query(User).first()
time.sleep(10)  # Без транзакции
with session.begin():
    user.name = "New Name"
    session.commit()

3. Мониторь старые snapshots:

-- Найти долгоживущие транзакции
SELECT 
    pid,
    usename,
    application_name,
    state,
    NOW() - query_start AS duration
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY query_start;

Заключение

Snapshots в БД — это фундаментальный механизм для безлокирующей конкурентности. Они позволяют:

  1. Читать данные без блокировок
  2. Обеспечивать консистентность транзакций
  3. Избегать race conditions
  4. Поддерживать высокую пропускную способность

Понимание snapshots критично для написания правильного многопоточного кода и оптимизации производительности БД.

Что такое сниппет (snippet)? | PrepBro