ВЫБЕРИТЕ данные, которые могут находиться под СЕРИАЛИЗУЕМОЙ блокировкой

#sql-server

#sql-server

Вопрос:

У меня есть часть приложения, которая постоянно обновляет значения в строках таблицы (1-100 строк). Поскольку эта целостность данных важна, я использую СЕРИАЛИЗУЕМУЮ блокировку транзакции в функциях, считывающих и обновляющих эти строки.

Теперь мой вопрос в том, выполняю ли я простой выбор только для чтения (без блокировки) для строк, которые в настоящее время используются транзакцией, я, вероятно, мог бы получить исключение ВЗАИМОБЛОКИРОВКИ, верно? Значит ли это, что мне все равно понадобится механизм повтора в случае ВЗАИМОБЛОКИРОВКИ даже в случае простого выбора?

Комментарии:

1. Для выбора чтения мне действительно нужно вернуть последнее зафиксированное состояние записей, поэтому в принципе я вообще не забочусь о целостности.

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

3. База данных MSSQL.

4. Я не обязательно ставлю под сомнение необходимость СЕРИАЛИЗУЕМОГО уровня изоляции, но я просто скажу, что на самом деле это необычно. Какие конкретные явления чтения вы пытаетесь предотвратить (т.Е. неповторяемые / фантомные чтения)?

5. Я использую его для получения согласованного состояния в системе входа в систему. Для обеспечения согласованности требуется, чтобы никакая другая транзакция не разрешала чтение или запись строк, и, насколько я понимаю, это можно сделать только с СЕРИАЛИЗУЕМОЙ блокировкой?

Ответ №1:

Теперь, когда я знаю, каков ваш конкретный бизнес-сценарий (из комментариев), вот как вы могли бы сделать что-то вроде того, что вы предлагаете, без необходимости реализации сериализуемой изоляции

 CREATE PROCEDURE [dbo].[updateUserState] (@UserID int)
AS
BEGIN
    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    BEGIN TRANSACTION

        SELECT [State]
        FROM [dbo].[UserState] WITH (UPDLOCK)
        WHERE [UserID] = @UserID;

        IF ([State] = 'logged out')
        BEGIN
            UPDATE [us]
            SET [State] = 'logged in'
            FROM [dbo].[UserState] AS [us]
            WHERE [UserID] = @UserID;
        END

    COMMIT TRANSACTION
END
  

Обратите внимание, что это упрощено, но отражает основную идею. UPDLOCK Подсказка в SELECT инструкции является ключом. В нем говорится: «попробуйте выбрать данные, поскольку я собирался выполнить обновление (что вы и делаете! чуть позже) и сохраните его до конца транзакции «. В вашем примере, если T2 входит, а T1 все еще работает, T2 не сможет получить блокировку обновления и, таким образом, будет ждать, пока T1 не завершится (успешно или нет). Также обратите внимание, что явная настройка уровня изоляции транзакции предназначена только для полноты; READ COMMITTED это значение по умолчанию в SQL Server.

Комментарии:

1. Да, вы совершенно правы, ключ — UPDLOCK. После некоторых исследований я теперь понимаю это. Теперь осталось только найти, как выполнить ВЫБОР с блокировкой обновления в Entity Framework.

2. Вы можете использовать хранимые процедуры с EF. Когда вам нужен этот уровень контроля, это хороший вариант использования.

3. Честно говоря, я предпочитаю избегать их, поэтому я, вероятно, просто использую обычный оператор SQL для извлечения данных. Тем не менее, его странный EF пропускает эту функциональность, поскольку кажется, что это было бы довольно стандартной задачей.

4. Похоже, мне все равно понадобится СЕРИАЛИЗУЕМАЯ транзакция, поскольку могут возникать фантомные строки. Поскольку при транзакции мне нужно будет заблокировать диапазон изменяемых или создаваемых состояний пользователя. Например, ВЫБЕРИТЕ [State] ИЗ [dbo]. [UserState] WITH (UPDLOCK) WHERE [userId] = @userId; Это выберет СОСТОЯНИЕ ПОЛЬЗОВАТЕЛЯ, но если ни одна существующая транзакция не попытается вставить новую, но это не заблокирует вторую транзакцию, выполняющую то же самое. Я прав в своем предположении?

5. Возможно, возможно, нет. В самом строгом смысле вы правы; если вы посмотрите только на эту таблицу, и возможно, что запись для этого пользователя не существует. Но у вас есть варианты. Если UserState связывает пользовательскую запись, вы можете выбрать ее в качестве прокси-сервера. Кроме того, если вы создаете запись UserState всякий раз, когда создаете пользователя, она гарантированно будет там, и вам не нужно беспокоиться о фантомных данных. В любом случае, суть в том, что существует несколько способов решения проблемы.