Какой уровень изоляции транзакций допускает «грязное чтение» (Dirty Read)?

Ответ

Проблема "грязного чтения" (Dirty Read) может возникнуть только на самом низком уровне изоляции транзакций — Read Uncommitted.

Что такое "грязное чтение"? Это ситуация, когда одна транзакция читает данные, которые были изменены другой транзакцией, но еще не зафиксированы (не был выполнен COMMIT). Если вторая транзакция в итоге будет отменена (ROLLBACK), первая транзакция будет работать с неверными, никогда не существовавшими данными.

Для сравнения, другие уровни изоляции (Read Committed, Repeatable Read, Serializable) предотвращают эту проблему, обеспечивая более высокую согласованность данных.

Пример на Go:

// Транзакция 1: Начинает изменять данные
tx1, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadUncommitted})
if err != nil { log.Fatal(err) }
// Уменьшаем баланс, но пока не коммитим
_, err = tx1.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil { log.Fatal(err) }

// Транзакция 2: В это же время читает данные
tx2, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadUncommitted})
if err != nil { log.Fatal(err) }
var balance int
// Читает НЕЗАФИКСИРОВАННЫЕ данные из Транзакции 1
err = tx2.QueryRow("SELECT balance FROM accounts WHERE id = 1").Scan(&balance)
if err != nil { log.Fatal(err) }
// balance уже будет показывать новое значение, хотя tx1 еще не завершена.

// Если Транзакция 1 откатится (ROLLBACK), то Транзакция 2 будет работать с неверными, "грязными" данными.
tx1.Rollback()

Из-за риска работы с несогласованными данными, которые могут быть отменены, уровень Read Uncommitted используется крайне редко.

Ответ 18+ 🔞

Слушай, а давай разберём эту дичь про "грязное чтение", а то народ путается, как хуй с пальмой. Суть в том, что эта хрень может случиться только на самом низком, позорном уровне изоляции — Read Uncommitted.

Что это за зверь такой, "грязное чтение"? Представь, одна транзакция начала что-то писать в базу, но ещё не сказала "окей, всё, я закончила" (COMMIT). А вторая, наглец, уже лезет и читает эти сырые, неутверждённые данные. Если первая потом передумает и откатит всё нахуй (ROLLBACK), то вторая останется с цифрами, которые никогда по-настоящему не существовали. Классический пиздец и источник говноданных.

Все остальные, более высокие уровни (Read Committed, Repeatable Read, Serializable) эту дыру прикрывают, не дают читать незакоммиченную хуйню.

Вот тебе живой пример на Go, чтобы совсем понятно стало:

// Транзакция 1: Начинает мутить с балансом
tx1, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadUncommitted})
if err != nil { log.Fatal(err) }
// Списала сотку, но сидит, думает — коммитить или нет.
_, err = tx1.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil { log.Fatal(err) }

// Транзакция 2: А тут уже нетерпеливая, с тем же уровнем изоляции
tx2, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadUncommitted})
if err != nil { log.Fatal(err) }
var balance int
// И читает, блядь, эти ПРЕДВАРИТЕЛЬНЫЕ цифры из первой транзакции!
err = tx2.QueryRow("SELECT balance FROM accounts WHERE id = 1").Scan(&balance)
if err != nil { log.Fatal(err) }
// В balance уже лежит новое значение, хотя tx1 ещё даже не решила, оставить его или нет.

// А если первая транзакция вдруг одумается и сделает Rollback? Вторая останется с полной хуйней в руках.
tx1.Rollback()

Из-за этого ебаного риска работать с данными, которые могут испариться, уровень Read Uncommitted используют реже, чем зубочистку после шашлыка. Только в очень специфичных случаях, когда скорость чтения критична, а на консистентность насрать. В остальном — просто игрушка для самоубийц.