Вставить заявление / Хранимые блокировки Proc - PullRequest
4 голосов
/ 19 октября 2010

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

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

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

Вот код. Оператор select похож на тот, который использовался linq, когда он делал свой собственный запрос. Я просто хочу посмотреть, существует ли элемент, а если нет, то вставить его. Я могу найти систему либо по PK, либо по некоторым поисковым значениям.

       SET NOCOUNT ON;
       BEGIN TRY
        SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

        BEGIN TRANSACTION SPFindContractMachine
        DECLARE @id int;
        set @id = (select [m].pkID from Machines as [m]
                        WHERE ([m].[fkContract] = @fkContract) AND ((
                        (CASE 
                            WHEN @bByID = 1 THEN 
                                (CASE 
                                    WHEN [m].[pkID] = @nMachineID THEN 1
                                    WHEN NOT ([m].[pkID] = @nMachineID) THEN 0
                                    ELSE NULL
                                 END)
                            ELSE 
                                (CASE 
                                    WHEN ([m].[iA_Metric] = @lA) AND ([m].[iB_Metric] = @lB) AND ([m].[iC_Metric] = @lC) THEN 1
                                    WHEN NOT (([m].[iA_Metric] = @lA) AND ([m].[iB_Metric] = @lB) AND ([m].[iC_Metric] = @lC)) THEN 0
                                    ELSE NULL
                                 END)
                         END)) = 1));
        if (@id IS NULL)
        begin
            Insert into Machines(fkContract, iA_Metric, iB_Metric, iC_Metric, dteFirstAdded) 
                values (@fkContract, @lA, @lB, @lC, GETDATE());

            set @id = SCOPE_IDENTITY();
        end

        COMMIT TRANSACTION SPFindContractMachine

        return @id;

    END TRY
    BEGIN CATCH
        if @@TRANCOUNT > 0
            ROLLBACK TRANSACTION SPFindContractMachine
    END CATCH

Ответы [ 4 ]

7 голосов
/ 19 октября 2010

Любая процедура, которая следует за шаблоном:

BEGIN TRAN
check if row exists with SELECT
if row doesn't exist INSERT
COMMIT

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

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

Другой альтернативой является использование оператора MERGE :

create procedure usp_getOrCreateByMachineID
    @nMachineId int output,
    @fkContract int,
    @lA int,
    @lB int,
    @lC int,
    @id int output
as
begin
    declare @idTable table (id int not null);
    merge Machines as target
        using (values (@nMachineID, @fkContract, @lA, @lB, @lC, GETDATE()))
            as source (MachineID, ContractID, lA, lB, lC, dteFirstAdded)
    on (source.MachineID = target.MachineID)
    when matched then
        update set @id = target.MachineID
    when not matched then
        insert (ContractID, iA_Metric, iB_Metric, iC_Metric, dteFirstAdded)
        values (source.contractID, source.lA, source.lB, source.lC, source.dteFirstAdded)
    output inserted.MachineID into @idTable;
    select @id = id from @idTable;
end 
go

create procedure usp_getOrCreateByMetrics
    @nMachineId int output,
    @fkContract int,
    @lA int,
    @lB int,
    @lC int,
    @id int output
as
begin
    declare @idTable table (id int not null);
    merge Machines as target
        using (values (@nMachineID, @fkContract, @lA, @lB, @lC, GETDATE()))
            as source (MachineID, ContractID, lA, lB, lC, dteFirstAdded)
    on (target.iA_Metric = source.lA
        and target.iB_Metric = source.lB
        and target.iC_Metric = source.lC)
    when matched then
        update set @id = target.MachineID
    when not matched then
        insert (ContractID, iA_Metric, iB_Metric, iC_Metric, dteFirstAdded)
        values (source.contractID, source.lA, source.lB, source.lC, source.dteFirstAdded)
    output inserted.MachineID into @idTable;
    select @id = id from @idTable;
end 
go

Этот пример разделяет два случая, поскольку запросы T-SQL не должны никогда пытаться разрешить два разных решения в одном запросе (результат никогда не оптимизируется). Так как две задачи под рукой (получить по идентификатору mahcine и получить по метрике) полностью разделены, процедуры должны быть отдельными, и вызывающая сторона должна вызывать соответствующую процедуру, а не передавать флаг. В этом примере показано, как достичь (вероятно) желаемого результата с помощью MERGE, но, конечно, правильное и оптимальное решение зависит от фактической схемы (определение таблицы, индексы и существующие указатели на месте). ) и о фактических требованиях (не ясно, что должна делать процедура, если критерии уже соответствуют, а не выводятся и @id?).

Устраняя СЕРИАЛИЗИРУЕМУЮ развязку, это больше не гарантирует для взаимоблокировки, но может все еще оставаться в тупике. Решение взаимоблокировки, конечно же, полностью зависит от схемы, которая не была указана, поэтому решение взаимоблокировки не может быть предоставлено в этом контексте. Кувалда блокирует все возможные строки (принудительно UPDLOCK или даже TABLOCX), но такое решение может снизить пропускную способность при интенсивном использовании, поэтому я не могу рекомендовать это без знания варианта использования.

2 голосов
/ 19 октября 2010

Как насчет этого SQL?Он перемещает проверку существующих данных и вставку в один оператор.Таким образом, когда два потока работают, они не зашли в тупик, ожидая друг друга.В лучшем случае второй поток блокируется в ожидании первого потока, но как только первый поток завершается, второй поток может выполняться.

BEGIN TRY

  BEGIN TRAN SPFindContractMachine

  INSERT INTO Machines (fkContract, iA_Metric, iB_Metric, iC_Metric, dteFirstAdded)
    SELECT @fkContract, @lA, @lB, @lC, GETDATE()
      WHERE NOT EXISTS (
        SELECT * FROM Machines
        WHERE fkContract = @fkContract
        AND ((@bByID = 1 AND pkID = @nMachineID)
             OR
             (@bByID <> 1 AND iA_Metric = @lA AND iB_Metric = @lB AND iC_Metric = @lC))

  DECLARE @id INT

  SET @id = (
    SELECT pkID FROM Machines
    WHERE fkContract = @fkContract
    AND ((@bByID = 1 AND pkID = @nMachineID)
         OR
         (@bByID <> 1 AND iA_Metric = @lA AND iB_Metric = @lB AND iC_Metric = @lC)))

  COMMIT TRAN SPFindContractMachine

  RETURN @id

END TRY
BEGIN CATCH
  IF @@TRANCOUNT > 0
    ROLLBACK TRAN SPFindContractMachine
END CATCH

Я также изменил эти операторы CASE на предложения ORed только потому, что их было легче читатьмне.Если я вспомню свою теорию SQL, ORing может сделать этот запрос немного медленнее.

2 голосов
/ 19 октября 2010

Избавьтесь от сделки. Это на самом деле не помогает вам, а приносит вам вред. Это должно решить вашу проблему.

1 голос
/ 19 октября 2010

Интересно, исправит ли это подсказка UPDLOCK к предыдущим SELECT? следует избегать сценариев взаимоблокировки, предотвращающих повторную блокировку чтения данных, которые вы собираетесь мутировать.

...