Что такое уровни изоляции транзакций в базах данных?

Ответ

Уровни изоляции транзакций определяют степень видимости изменений, выполняемых параллельными транзакциями, и защищают от стандартных проблем конкурентного доступа. Выбор уровня — это компромисс между согласованностью данных и производительностью (параллелизмом).

Основные проблемы, от которых защищают уровни изоляции:

  1. Грязное чтение (Dirty Read): Чтение незафиксированных данных другой транзакции, которые могут быть откатаны.
  2. Неповторяющееся чтение (Non-repeatable Read): Получение разных значений одного и того же поля при повторном чтении в рамках одной транзакции из-за коммита другой транзакции.
  3. Фантомное чтение (Phantom Read): Появление новых или исчезновение существующих строк при повторном выполнении одного и того же запроса из-за коммита другой транзакции.
Уровни изоляции (от меньшей к большей): Уровень Грязное чтение Неповторяющееся чтение Фантомное чтение Механизм
Read Uncommitted ❌ Возможно Возможно Возможно Блокировок на чтение нет.
Read Committed (по умолчанию в SQL Server, PostgreSQL) ✅ Защищено ❌ Возможно Возможно Блокировка изменяемой строки до коммита.
Repeatable Read ✅ Защищено ✅ Защищено ❌ Возможно Блокировка читаемых и изменяемых строк.
Serializable ✅ Защищено ✅ Защищено ✅ Защищено Блокировка диапазонов ключей или таблиц.

Пример использования в C# (Entity Framework Core):

using var context = new AppDbContext();
using var transaction = await context.Database.BeginTransactionAsync(
    System.Data.IsolationLevel.Serializable // Самый строгий уровень
);

try
{
    // Критическая операция, требующая полной изоляции
    var balance = await context.Accounts
        .Where(a => a.Id == accountId)
        .Select(a => a.Balance)
        .FirstAsync();

    if (balance >= amount)
    {
        // Списание средств...
        await context.SaveChangesAsync();
        await transaction.CommitAsync(); // Фиксация изменений
    }
}
catch (Exception)
{
    await transaction.RollbackAsync(); // Откат в случае ошибки
    throw;
}

Ответ 18+ 🔞

А, слушай, вот эти ваши уровни изоляции транзакций — это вообще отдельная песня, блядь. Ну, представь себе: у тебя база данных, и в неё одновременно лезут, как тараканы на кухню ночью, куча разных запросов. И если их не упорядочить, начнётся такая каша, что потом не разберёшь, кто кому и сколько должен. Вот как раз чтобы этого не было, и придумали эти уровни — типа правила, кто что и когда видит.

Основные проблемы, от которых они спасают, это вообще классика жанра, ёпта.

Первая — Грязное чтение. Это когда ты читаешь данные, которые другая транзакция ещё даже не зафиксировала, типа на полпути. А она потом взяла и откатилась! И ты остаёшься с цифрами, которых на самом деле никогда не было. Представь, смотришь баланс, видишь миллион, радуешься, а потом — хоп! — и этот миллион испарился, потому что операция не прошла. Полный пиздец, короче.

Вторая — Неповторяющееся чтение. Вот ты в рамках своей транзакции прочитал какое-то значение, допустим, остаток на складе. Подумал, решил что-то сделать, и через секунду читаешь его снова — а там уже другая цифра! Потому что параллельная транзакция успела закоммитить своё изменение. И ты сидишь такой: «Я чё, ослеп? Только же было десять, а теперь девять!». Волнение ебать, да?

Третья — Фантомное чтение. Это ещё хитрее. Ты выполнил запрос, получил набор строк. Потом, через мгновение, выполняешь его же ещё раз — а там уже другие строки! Появились новые или старые исчезли. Как будто призраки, сука. Только что их не было, и вот они уже есть. Чувствуешь себя идиотом, честное слово.

Так вот, чтобы со всем этим бороться, есть четыре уровня изоляции. Идут они как нарастающая — от «похуй, делайте что хотите» до «все стройся в одну очередь и не дышите».

  1. Read Uncommitted — это вообще анархия, блядь. Никаких блокировок на чтение. Ты можешь читать что угодно, в том числе и те самые «грязные», незафиксированные данные. Производительность, может, и высокая, но согласованность данных — ниже плинтуса. Как ходить по минному полю в темноте.
  2. Read Committed — уже более-менее. Это часто уровень по умолчанию, например, в PostgreSQL. Он защищает от грязного чтения, но от неповторяющегося и фантомного — нет. Механизм простой: когда транзакция что-то меняет, она эту строку блокирует до своего коммита, чтобы другие не видели промежуточный результат. Но как только коммит прошёл — все увидят новое значение.
  3. Repeatable Read — тут уже серьёзнее. Он гарантирует, что если ты в своей транзакции что-то прочитал, то до её конца эти данные не изменятся. От грязного и неповторяющегося чтения защита есть. Но фантомы, блядь, всё ещё могут проскакивать! Новые строки могут материализоваться из ниоткуда.
  4. Serializable — это царь и бог, самый строгий уровень. Полная изоляция, как будто транзакции выполняются строго одна за другой. Никаких грязных чтений, никаких изменений прочитанного, никаких фантомов. Достигается это жёсткими блокировками целых диапазонов или даже таблиц. Производительность, понятное дело, может просесть конкретно, но зато данные в целости и сохранности.

Вот, например, как это в коде на C# с Entity Framework выглядит, когда тебе позарез нужно, чтобы всё было строго:

using var context = new AppDbContext();
using var transaction = await context.Database.BeginTransactionAsync(
    System.Data.IsolationLevel.Serializable // Берём самый серьёзный уровень, нахуй
);

try
{
    // Допустим, операция критическая, типа перевода бабла
    var balance = await context.Accounts
        .Where(a => a.Id == accountId)
        .Select(a => a.Balance)
        .FirstAsync();

    if (balance >= amount)
    {
        // Если хватает — списываем
        // ... тут логика списания ...
        await context.SaveChangesAsync();
        await transaction.CommitAsync(); // Всё ок, фиксируем нахуй
    }
}
catch (Exception)
{
    // Если что-то пошло не так — откатываем всё к чёртовой матери
    await transaction.RollbackAsync();
    throw;
}

Короче, выбор уровня — это вечный компромисс. Либо ты жертвуешь скоростью ради полной гарантии, либо живёшь на грани, надеясь, что тебе не попадётся та самая редкая ситуация, которая всё похерит. Как в жизни, ёпта.