Опишите стандартные уровни изоляции транзакций в базах данных.

Ответ

Уровни изоляции транзакций — это настройка в СУБД, которая определяет, насколько одна транзакция "видит" изменения, сделанные в параллельных транзакциях. Стандарт SQL определяет четыре основных уровня:

  1. Read Uncommitted (Чтение незафиксированных данных)

    • Описание: Самый низкий уровень. Транзакция может читать данные, которые были изменены другой транзакцией, но еще не зафиксированы (не применены через COMMIT).
    • Проблема: Позволяет "грязное чтение" (dirty read).
  2. Read Committed (Чтение зафиксированных данных)

    • Описание: Транзакция видит только те изменения, которые были зафиксированы другими транзакциями. Это уровень по умолчанию для многих СУБД (например, PostgreSQL, Oracle).
    • Проблема: Защищает от "грязного чтения", но возможна проблема "неповторяемого чтения" (non-repeatable read) — повторное чтение одной и той же строки в рамках одной транзакции может вернуть разные данные.
  3. Repeatable Read (Повторяемое чтение)

    • Описание: Гарантирует, что если транзакция повторно читает одну и ту же строку, она получит те же самые данные. СУБД блокирует читаемые строки от изменений другими транзакциями.
    • Проблема: Защищает от "неповторяемого чтения", но возможна проблема "фантомного чтения" (phantom read) — когда при повторном чтении диапазона строк появляются новые строки, добавленные другой транзакцией.
  4. Serializable (Сериализуемый)

    • Описание: Самый строгий уровень. Гарантирует, что результат параллельного выполнения транзакций будет таким же, как если бы они выполнялись последовательно. Защищает от всех вышеперечисленных проблем.
    • Цена: Наибольшее влияние на производительность из-за использования блокировок.

Установка уровня изоляции в Go

В Go уровень изоляции указывается при старте транзакции с помощью объекта sql.TxOptions. Неверно думать, что он устанавливается на всё соединение.

package main

import (
    "context"
    "database/sql"
    "log"
)

func main() {
    db, err := sql.Open("postgres", "your_connection_string")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    ctx := context.Background()

    // Запускаем транзакцию с уровнем изоляции Serializable
    txOpts := &sql.TxOptions{Isolation: sql.LevelSerializable}
    tx, err := db.BeginTx(ctx, txOpts)
    if err != nil {
        log.Fatal(err)
    }

    // ... выполняем операции в транзакции ...

    // Завершаем транзакцию
    if err := tx.Commit(); err != nil {
        log.Fatal(err) // или tx.Rollback()
    }
}

Ответ 18+ 🔞

А, ну вот, блядь, подъехали к самому сокровенному — к уровням изоляции транзакций! Это ж такая штука, которая определяет, насколько одна транзакция может видеть, как другая, сука, шаловливые ручки к данным прикладывает. Четыре штуки их, как евангелистов, только пользы больше.

Ну, поехали, разбирать эту кашу.

Первый, «Чтение незафиксированных данных» (Read Uncommitted). Это, блядь, уровень для полных распиздяев. Транзакция может читать данные, которые другая транзакция только что накакала, но ещё даже не смыла, COMMIT не сделала. Представь, читаешь ты баланс счёта, а там миллион, потому что кто-то его только что пополнил, но потом этот кто-то передумал и откатился. А ты уже побежал шампанское заказывать. Это и есть «грязное чтение», ёпта. Пиздец, а не уровень.

Второй, «Чтение зафиксированных данных» (Read Committed). Вот это уже поумнее. Транзакция видит только то, что другие уже официально зафиксировали, то есть смыли в базу. От грязи защищает. Это, кстати, у многих баз дефолтный уровень — в PostgreSQL, в Oracle. Но и тут, сука, подвох есть. Называется «неповторяемое чтение». Сидишь ты, читаешь одну и ту же строку в своей транзакции. Прочитал — там 100 рублей. Отвлёкся на секунду, другая транзакция проскочила и изменила её на 50. Снова читаешь — оп, уже 50! Волнение ебать! Где мои деньги, блядь? Вот это и есть проблема.

Третий, «Повторяемое чтение» (Repeatable Read). А вот это уже серьёзный дядька. Он говорит: «Чувак, раз ты в рамках одной транзакции прочитал строку, то она для тебя заморожена». База её блокирует, чтобы другие шалуны не могли изменить. От «неповторяемого чтения» защита. Но, блядь, жизнь — она хитрая жопа. Появляется «фантомное чтение». Ты прочитал, допустим, всех пользователей из Москвы. Их 10 штук. Пока ты думаешь, что с ними делать, другая транзакция успевает нового москвича добавить. Ты снова делаешь тот же запрос — оп, нихуя себе, их уже 11! Откуда этот фантом взялся, ёпта? Вот оттуда.

Четвёртый, «Сериализуемый» (Serializable). А это, блядь, царь-уровень, тяжёлая артиллерия. Он гарантирует, что результат будет такой, будто все транзакции выполнялись строго по очереди, одна за другой. Никаких грязных, неповторяемых и фантомных чтений. Полный порядок, как в мавзолее. Но плата за это — производительность, потому что блокировок овердохуища. Это как если бы ты в банке один кассир на всех был.


А теперь, сука, главное, что все путают! В Go этот уровень НЕ выставляется на всё соединение с базой, как многие думают. Его задают конкретно для каждой транзакции, когда её начинаешь. Через эту штуку sql.TxOptions.

Смотри, как это выглядит в коде, чтобы не быть мудаком:

package main

import (
    "context"
    "database/sql"
    "log"
)

func main() {
    db, err := sql.Open("postgres", "your_connection_string")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    ctx := context.Background()

    // Вот тут, блядь, магия! Говорим: "Хочу транзакцию, и чтоб она была сериализуемая, как мои принципы!"
    txOpts := &sql.TxOptions{Isolation: sql.LevelSerializable}
    tx, err := db.BeginTx(ctx, txOpts)
    if err != nil {
        log.Fatal(err)
    }

    // ... тут твои бизнес-процессы, UPDATE, INSERT, SELECT ...

    // И либо фиксируем успех...
    if err := tx.Commit(); err != nil {
        log.Fatal(err) // ... либо, если всё пошло по пизде, откатываем: tx.Rollback()
    }
}

Вот и вся философия. Выбирай уровень по потребностям: хочешь скорость и готов к грязи — Read Uncommitted. Нужна стабильность и можно чуть подождать — Serializable. Главное — понимать, за что платишь. А то будет как с Герасимом и Муму, в рот меня чих-пых.