Минимальный уровень изоляции транзакций, чтобы избежать "Потерянных обновлений"

С уровнями изоляции транзакций SQL Server вы можете избежать некоторых нежелательных проблем concurrency, таких как грязные чтения и т.д.

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

Кален Делани в своей книге "Внутренние книги SQL Server" говорит (Глава 10 - Транзакции и concurrency - Страница 592):

В Read Uncommitted изоляции, все описанные выше поведения, кроме потерянных обновлений, возможны.

С другой стороны, независимый тренер SQL Server, предлагающий нам класс, сказал нам, что нам нужно хотя бы "Повторяемое чтение", чтобы избежать потерянных обновлений.

Итак, кто прав? И почему?

Ответы

Ответ 1

Пример в книге: Клерк А и Клерк Б, получающий отгрузки Виджеты.

Они оба проверяют текущий инвентарь, см. 25 на складе. Clerk A имеет 50 виджетов и обновлений до 75, Clerk B имеет 20 виджетов и поэтому обновляет до 45, перезаписывая предыдущее обновление.

Я предполагаю, что она имела в виду, что это явление можно избежать на всех уровнях изоляции Clerk A do

UPDATE Widgets
SET StockLevel = StockLevel + 50
WHERE ...

и Clerk B делает

UPDATE Widgets
SET StockLevel = StockLevel + 20
WHERE ...

Конечно, если SELECT и UPDATE выполняются как отдельные операции, вам понадобится repeatable read, чтобы избежать этого, поэтому блокировка S в строке сохраняется на время транзакции (что приведет к тупиковой ситуации в этом случае)

Ответ 2

Я не знаю, слишком ли поздно ответить, но я просто изучаю уровни изоляции транзакций в колледже, и как часть моих исследований я наткнулся на эту ссылку:

Microsoft Technet

В частности, данный параграф:

Утерянное обновление

Утерянное обновление можно интерпретировать одним из двух способов. В первом случае считается, что потерянное обновление произошло, когда данные, которые были обновлены одной транзакцией, перезаписаны другой транзакцией до того, как первая транзакция будет либо выполнена, либо отменена. Этот тип потерянного обновления не может произойти в SQL Server 2005, потому что он не разрешен ни при каком уровне изоляции транзакций.

Другая интерпретация потерянного обновления - это когда одна транзакция (транзакция № 1) считывает данные в свою локальную память, а затем другая транзакция (транзакция №2) изменяет эти данные и фиксирует их изменение. После этого Transaction # 1 обновляет одни и те же данные на основе того, что он читал в памяти до того, как была выполнена транзакция №2. В этом случае обновление, выполняемое транзакцией № 2, может считаться потерянным обновлением.

Таким образом, по сути, оба человека правы.

Лично (и я открыт, чтобы быть неправым, поэтому, пожалуйста, поправьте меня, поскольку я просто изучаю это). Я беру из этого следующие два момента:

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

  • Когда люди говорят о потерянных обновлениях, они знают, что применяется первый абзац, и, как правило, означает второй тип потерянного обновления.

Опять же, пожалуйста, поправьте меня, если что-то здесь не так, как я хотел бы это понять.

Ответ 3

Потерянные обновления могут возникать, даже если чтения и записи находятся в отдельных транзакциях, например, когда пользователи читают данные на веб-страницах, а затем обновляют. В таких случаях уровень изоляции не может защитить вас, особенно когда соединения повторно используются из пула соединений. Мы должны использовать другие подходы, такие как rowversion. Вот мой законченный ответ.

Ответ 4

Мой опыт заключается в том, что с Read Uncommitted вы больше не получаете "потерянных обновлений", однако вы можете получить "потерянные откаты". Тренер SQL, вероятно, имел в виду эту проблему concurrency, поэтому ответ, который вы, вероятно, ищете, - Repeatable Read.

Тем не менее, мне было бы очень интересно, есть ли у кого-то опыт, который противоречит этому.

Ответ 5

Как отметил Фрэнсис Роджерс, вы можете положиться на реализацию SQL Server, так это то, что после того, как транзакция обновила некоторые данные, каждый уровень изоляции всегда выдает "блокировки обновлений" по данным и отрицает обновления и записи из другой транзакции, независимо от того, уровень изоляции. Вы можете быть уверены, что такие потери потеряны.

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

Ваш ответ в этом сценарии либо использует повторное чтение в первой транзакции, либо может использовать некоторую блокировку чтения из первой транзакции по данным (я действительно не знаю об этом уверенным образом. возможно, это поможет любому, кто интересуется этим подходом Microsoft Designing Transactions и оптимизация блокировки).

Ответ 6

Ниже приведена цитата из 70-762 Developing SQL Databases (p. 212):

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

Таким образом, кажется, что ни один из уровней изоляции не может помочь вам в таких случаях, и вам нужно решить проблему в самом коде. Например:

DROP TABLE IF EXISTS [dbo].[Balance];

CREATE TABLE [dbo].[Balance]
(
    [BalanceID] TINYINT IDENTITY(1,1)
   ,[Balance] MONEY
   ,CONSTRAINT [PK_Balance] PRIMARY KEY
   (
        [BalanceID]
   )
);

INSERT INTO [dbo].[Balance] ([Balance])
VALUES (100);

-- query window 1
BEGIN TRANSACTION;

    DECLARE @CurrentBalance MONEY;

    SELECT @CurrentBalance = [Balance]
    FROM [dbo].[Balance]
    WHERE [BalanceID] = 1;

    WAITFOR DELAY '00:00:05'

    UPDATE [dbo].[Balance]
    SET [Balance] = @CurrentBalance + 20
    WHERE [BalanceID] = 1;

COMMIT TRANSACTION;

-- query window 2
BEGIN TRANSACTION;

    DECLARE @CurrentBalance MONEY;

    SELECT @CurrentBalance = [Balance]
    FROM [dbo].[Balance]
    WHERE [BalanceID] = 1;

    UPDATE [dbo].[Balance]
    SET [Balance] = @CurrentBalance + 50
    WHERE [BalanceID] = 1;

COMMIT TRANSACTION;

Создайте таблицу, выполните каждую часть кода в отдельных окнах запросов. Изменение уровня изоляции ничего не делает. Например, единственное различие между read committed и repeatable read состоит в том, что последний, блокирует вторую транзакцию, когда первый закончен, а затем перезаписывает значение.