← Назад к вопросам
Что такое проблема грязного чтения?
2.3 Middle🔥 121 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое проблема грязного чтения?
Грязное чтение (dirty read) — это серьёзная проблема конкурентного доступа в базах данных, при которой одна транзакция читает незафиксированные (не закоммиченные) изменения, сделанные другой транзакцией. Если вторая транзакция позже откатится (rollback), прочитанные данные становятся некорректными.
Визуальный пример проблемы
Время | Транзакция 1 (T1) | Транзакция 2 (T2)
-------|---------------------------|---------------------------
T0 | BEGIN TRANSACTION |
| | BEGIN TRANSACTION
T1 | SELECT balance = 1000 |
| (из базы) |
T2 | | UPDATE balance = 500
| | (изменение, но не коммит)
T3 | | SELECT balance = 500
| | ГРЯЗНОЕ ЧТЕНИЕ!
T4 | | ROLLBACK (отката
T5 | SELECT balance = ? (какое значение?)
| (ожидает T2) | Значение откачено!
Пример кода
// Транзакция 1: получение баланса счёта
public void transfer1() {
Transaction tx1 = session.beginTransaction();
try {
Account account = session.get(Account.class, 1);
System.out.println("T1: balance = " + account.getBalance());
Thread.sleep(2000); // Ждём T2
System.out.println("T1: balance = " + account.getBalance());
// Может быть другое значение, если T2 изменил и откатил!
tx1.commit();
}
}
// Транзакция 2: изменение баланса и откат
public void transfer2() {
Transaction tx2 = session.beginTransaction();
try {
Account account = session.get(Account.class, 1);
account.setBalance(500); // Грязное изменение
session.save(account);
// Без коммита!
Thread.sleep(500);
tx2.rollback(); // Отката всех изменений
}
}
Почему это проблема?
- Несогласованность данных — транзакция работает с данными, которые не будут существовать
- Потеря целостности — бизнес-логика может принять неправильные решения
- Каскадные ошибки — неправильные данные передаются дальше в систему
Пример из реальной жизни
// Система бронирования билетов
// Транзакция 1: проверяем количество билетов
int availableTickets = ticketRepository.countAvailable(eventId);
if (availableTickets > 0) {
// Грязное чтение! T2 может откатить своё удаление
}
// Транзакция 2 (параллельно):
ticketRepository.deleteTicket(ticketId);
// ... какая-то ошибка ...
rollback(); // Билет был откачен
Уровни изоляции транзакций
Проблема грязного чтения решается установкой нужного уровня изоляции:
// Java (Hibernate)
session.setDefaultTransactionIsolationLevel(
Connection.TRANSACTION_READ_COMMITTED
);
| Уровень | Грязное чтение | Неповторяемое чтение | Фантомное чтение |
|---|---|---|---|
| READ_UNCOMMITTED | ДА | ДА | ДА |
| READ_COMMITTED | НЕТ | ДА | ДА |
| REPEATABLE_READ | НЕТ | НЕТ | ДА |
| SERIALIZABLE | НЕТ | НЕТ | НЕТ |
READ_UNCOMMITTED (Грязное чтение возможно)
SET TRANSACTION ISOLATION LEVEL READ_UNCOMMITTED;
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- Может прочитать незакоммиченное
COMMIT;
READ_COMMITTED (Грязное чтение исключено)
SET TRANSACTION ISOLATION LEVEL READ_COMMITTED;
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- Читает только закоммиченные
COMMIT;
Решение в Java и Hibernate
// Установка уровня изоляции для всей сессии
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepository.findById(fromId).orElseThrow();
Account to = accountRepository.findById(toId).orElseThrow();
// Теперь грязное чтение исключено
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
}
// На уровне JDBC
Connection conn = dataSource.getConnection();
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
Statement stmt = conn.createStatement();
Пример с Hibernate
@Entity
@Table(name = "accounts")
public class Account {
@Id
private Long id;
private BigDecimal balance;
}
@Service
public class AccountService {
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Account from = hibernateTemplate.find(Account.class, fromId);
Account to = hibernateTemplate.find(Account.class, toId);
// Оба счёта получены в READ_COMMITTED
// Другие транзакции не могут влиять на наши данные
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
}
}
Издержки повышения уровня изоляции
Уровень изоляции | Защита | Производительность | Блокировки
--------------------|---------------|--------------------|----------
READ_UNCOMMITTED | Минимальная | Максимальная | Нет
READ_COMMITTED | Средняя | Высокая | Да
REPEATABLE_READ | Высокая | Средняя | Много
SERIALIZABLE | Полная | Низкая | Максимум
Когда выбрать уровень изоляции
- READ_UNCOMMITTED — только для отчётов с приблизительными данными
- READ_COMMITTED — стандарт для большинства приложений (финансы, платежи)
- REPEATABLE_READ — когда нужна согласованность одного ресурса
- SERIALIZABLE — редко, только для критичных операций
Проблема грязного чтения — одна из самых серьёзных в многопользовательских системах, и её решение через правильный уровень изоляции транзакций — это критический аспект разработки надёжных приложений.