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

В чем разница между READ COMMITTED и REPEATABLE READ?

3.0 Senior🔥 141 комментариев
#Базы данных (SQL)

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

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

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

Различия между READ COMMITTED и REPEATABLE READ

Eto уровни изоляции транзакций в базах данных (особенно в PostgreSQL и MySQL). Они определяют, какие данные может видеть одна транзакция из изменений, сделанных другими параллельными транзакциями. Это критически важно для обеспечения консистентности и отсутствия гонок данных.

Определение уровней изоляции

READ COMMITTED (Чтение с коммитом)

Это уровень изоляции по умолчанию в PostgreSQL и одном из стандартных в MySQL. Транзакция может видеть только данные, которые были закоммичены другими транзакциями, но НЕ видит несовершённые (в процессе) изменения.

# Пример READ COMMITTED
# Транзакция A                     | Транзакция B
# ============================================
# BEGIN;                           |
#                                  | BEGIN;
# SELECT balance FROM accounts;    |
# (видит balance = 1000)           |
#                                  | UPDATE accounts SET balance = 500;
# SELECT balance FROM accounts;    |
# (видит balance = 1000)  ← ещё не закоммичено
#                                  | COMMIT;  ← теперь закоммичено
# SELECT balance FROM accounts;    |
# (видит balance = 500)    ← видит изменение!
COMMIT;                          |

Проблема READ COMMITTED — Dirty Read не происходит, но может быть Non-repeatable Read.

# SQL пример
from django.db import connection, transaction

with transaction.atomic():
    # READ COMMITTED
    with connection.cursor() as cursor:
        # Установить уровень изоляции
        cursor.execute("SET TRANSACTION ISOLATION LEVEL READ COMMITTED;")
        
        cursor.execute("SELECT balance FROM accounts WHERE id = 1;")
        balance1 = cursor.fetchone()[0]  # 1000
        
        # Другая транзакция коммитит изменения
        # ...
        
        cursor.execute("SELECT balance FROM accounts WHERE id = 1;")
        balance2 = cursor.fetchone()[0]  # Может быть 500!
        
        # balance1 != balance2 — Non-repeatable Read!

REPEATABLE READ (Повторяемое чтение)

Транзакция видит снимок данных на момент начала, и все SELECT запросы видят одни и те же данные (исходный снимок), даже если другие транзакции совершили изменения и закоммитили их.

# Пример REPEATABLE READ
# Транзакция A                     | Транзакция B
# ============================================
# BEGIN;                           |
#                                  | BEGIN;
# SELECT balance FROM accounts;    |
# (видит balance = 1000)           |
#                                  | UPDATE accounts SET balance = 500;
#                                  | COMMIT;
# SELECT balance FROM accounts;    |
# (видит balance = 1000)  ← всё ещё видит снимок!
#                                  |
# SELECT balance FROM accounts;    |
# (видит balance = 1000)  ← всегда видит одно и то же
COMMIT;                          |

В PostgreSQL REPEATABLE READ использует MVCC (Multi-Version Concurrency Control).

# SQL пример
from django.db import connection, transaction

with transaction.atomic():
    # REPEATABLE READ
    with connection.cursor() as cursor:
        cursor.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;")
        
        cursor.execute("SELECT balance FROM accounts WHERE id = 1;")
        balance1 = cursor.fetchone()[0]  # 1000
        
        # Другая транзакция коммитит изменения
        # ...
        
        cursor.execute("SELECT balance FROM accounts WHERE id = 1;")
        balance2 = cursor.fetchone()[0]  # Всё ещё 1000!
        
        # balance1 == balance2 ← гарантировано!
        assert balance1 == balance2

Сравнительная таблица

ПроблемаREAD COMMITTEDREPEATABLE READ
Dirty Read✅ Не происходит✅ Не происходит
Non-repeatable Read❌ Происходит✅ Не происходит
Phantom Read❌ Происходит⚠️ В некоторых СУБД происходит
Производительность⚡ Быстрее🔄 Медленнее
Конкурентность🔄 Выше🔒 Ниже

Типы проблем в параллельных транзакциях

Dirty Read — чтение грязных данных

Транзакция видит незакоммиченные изменения другой транзакции.

# Пример Dirty Read
# Транзакция A                     | Транзакция B
# ============================================
#                                  | BEGIN;
#                                  | UPDATE balance SET amount = 500;
# BEGIN;                           |
# SELECT balance;                  |
# (видит 500 — незакоммиченное!)  |
#                                  | ROLLBACK;  ← откатывается!
# SELECT balance;  ← видит 1000    |
# (данные были откачены!)          |
COMMIT;                          |

READ COMMITTED предотвращает Dirty Read.

Non-repeatable Read — неповторяемое чтение

Транзакция видит разные значения при повторном чтении тех же данных.

# Пример Non-repeatable Read
# Транзакция A                     | Транзакция B
# ============================================
# BEGIN;                           |
# SELECT balance;                  |
# (видит 1000)                     |
#                                  | BEGIN;
#                                  | UPDATE balance SET amount = 800;
#                                  | COMMIT;
# SELECT balance;                  |
# (видит 800)  ← другое значение!  |
COMMIT;                          |

READ COMMITTED НЕ предотвращает Non-repeatable Read. REPEATABLE READ предотвращает Non-repeatable Read.

Phantom Read — чтение фантомов

Транзакция видит новые строки, которые не видела в начале.

# Пример Phantom Read
# Транзакция A                     | Транзакция B
# ============================================
# BEGIN;                           |
# SELECT COUNT(*) FROM orders;     |
# (возвращает 5)                   |
#                                  | BEGIN;
#                                  | INSERT INTO orders (...);  ← новая строка
#                                  | COMMIT;
# SELECT COUNT(*) FROM orders;     |
# (возвращает 6)  ← фантомная строка!
COMMIT;                          |

Ни READ COMMITTED, ни REPEATABLE READ полностью не предотвращают Phantom Read.

Практические примеры в Django

READ COMMITTED (по умолчанию)

from django.db import transaction
from myapp.models import Account

@transaction.atomic(durable=True)
def transfer_money_read_committed():
    # Django по умолчанию использует READ COMMITTED
    account_a = Account.objects.select_for_update().get(id=1)
    account_b = Account.objects.select_for_update().get(id=2)
    
    # Если другая транзакция обновила эти счёта и закоммитила,
    # мы видим новые значения
    print(f"A balance: {account_a.balance}")  # Может видеть свежие данные
    print(f"B balance: {account_b.balance}")
    
    account_a.balance -= 100
    account_b.balance += 100
    account_a.save()
    account_b.save()

REPEATABLE READ

from django.db import transaction, connection
from myapp.models import Account

@transaction.atomic(durable=True)
def transfer_money_repeatable_read():
    # Установить REPEATABLE READ
    with connection.cursor() as cursor:
        if connection.vendor == 'postgresql':
            cursor.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;")
        elif connection.vendor == 'mysql':
            cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;")
    
    account_a = Account.objects.select_for_update().get(id=1)
    account_b = Account.objects.select_for_update().get(id=2)
    
    # Видим снимок на момент начала транзакции
    balance_a_first = account_a.balance
    balance_b_first = account_b.balance
    
    # Даже если другая транзакция обновит и закоммитит,
    # мы всё ещё видим исходные значения
    account_a.refresh_from_db()  # Может быть из кеша
    balance_a_second = account_a.balance
    
    assert balance_a_first == balance_a_second  # Гарантировано!
    
    account_a.balance -= 100
    account_b.balance += 100
    account_a.save()
    account_b.save()

Выбор уровня изоляции

Используй READ COMMITTED когда:

  • Нужна высокая конкурентность
  • Можешь обработать Non-repeatable Read
  • Большинство операций чтение-написание
  • Используешь optimistic locking или версионирование
# Optimistic locking с версией
class Account(models.Model):
    balance = models.DecimalField()
    version = models.IntegerField(default=0)
    
    def update_balance(self, new_balance):
        updated = Account.objects.filter(
            id=self.id,
            version=self.version
        ).update(balance=new_balance, version=F('version') + 1)
        
        if updated == 0:
            raise ValueError("Версия изменилась, повтори операцию")

Используй REPEATABLE READ когда:

  • Нужна консистентность
  • Выполняешь сложные многошаговые операции
  • Нужна гарантия, что данные не изменятся в процессе
  • Могущи пожертвовать конкурентностью
# Сложная финансовая операция
@transaction.atomic(durable=True)
def complex_financial_operation():
    with connection.cursor() as cursor:
        cursor.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;")
    
    # Все SELECT'ы видят одинаковый снимок данных
    accounts = list(Account.objects.all())
    total = sum(acc.balance for acc in accounts)
    
    # Если другая транзакция добавила аккаунт, мы его не видим
    # Это гарантирует консистентность отчёта

Важные замечания

PostgreSQL специфика:

  • REPEATABLE READ в PostgreSQL более строгий, чем стандарт SQL
  • Использует снимки MVCC для полной изоляции
  • Есть также SERIALIZABLE уровень для полной сериализации

MySQL специфика:

  • REPEATABLE READ — уровень по умолчанию
  • Использует gap locks для предотвращения Phantom Read

Выбор правильного уровня изоляции критически важен для:

  • Финансовых систем (используй REPEATABLE READ или выше)
  • E-commerce (READ COMMITTED с optimistic locking)
  • Аналитики (READ COMMITTED, не важен snapshot)

Понимание различий между уровнями изоляции критически важно для разработки надёжных и конкурентных систем баз данных.

В чем разница между READ COMMITTED и REPEATABLE READ? | PrepBro