В чем разница между READ COMMITTED и REPEATABLE READ?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Различия между 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 COMMITTED | REPEATABLE 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)
Понимание различий между уровнями изоляции критически важно для разработки надёжных и конкурентных систем баз данных.