Блокировка в одновременно выполняемых вложенных хранимых процедурах - PullRequest
0 голосов
/ 19 октября 2019

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

ПРИМЕЧАНИЕ

Пожалуйста, игнорируйте любые проблемы синтаксиса или орфографии или отсутствие декларируемых переменных. Я вырезал много ненужного кода, чтобы максимально упростить его, пытаясь убедиться, что всеобъемлющая стратегия все еще действует. Пожалуйста, примите во внимание этот «псевдокод». Я понимаю, что фактические запросы, выполняемые в дочерних процессах, были бы полезны при выяснении причины, но я в основном заинтересован в том, чтобы выяснить, является ли способ использования транзакций во вложенных процедурах правильным, и еслиуровень изоляции транзакции, который я использую, является правильным. Кроме того, в отношении решений мне интересно, является ли программная повторная обработка взаимоблокировок допустимым обходным решением или мне следует искать другое решение.

Так что этородительская процедура:

CREATE PROCEDURE [dbo].[ParentProc]
    @BatchSize int = 5
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON; --http://www.sommarskog.se/error_handling/Part1.html#jumpXACT_ABORT
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; --mandated by DBAs unless I provide good reason not to use it.

    /*
    I opted to use UPDLOCK and READPAST hints in this proc based on doing some research online to determine how best to handle a queue processing system, which is essentially what we're doing.
    For reference: https://www.mssqltips.com/sqlservertip/1257/processing-data-queues-in-sql-server-with-readpast-and-updlock/
    */


    BEGIN TRY
        --this transaction just marks the records with a guid that this instance of the proc will be handling.
        BEGIN TRANSACTION
            DECLARE @ProcessID varchar(36) = (SELECT NEWID());

            ;with cte as (
                SELECT TOP (@BatchSize) ProcessID, UpdateDate
                FROM dbo.MainJSONTable
                WHERE IsProcessed = 0 
                AND ProcessID IS NULL
                AND ProcessingFailed = 0
                ORDER BY CreateDate ASC
            )
            UPDATE cte WITH (UPDLOCK, READPAST)
                SET ProcessID = @ProcessID,
                UpdateDate = GETUTCDATE();

            DECLARE @i bigint = 1;

            SET @BatchSize = (SELECT COUNT(*) FROM dbo.MainJSONTable WHERE IsProcessed = 0 AND ProcessingFailed = 0 AND ProcessID = @ProcessID);

        COMMIT TRANSACTION;

    END TRY

    BEGIN CATCH
        IF (@@TRANCOUNT > 0)
        BEGIN
            ROLLBACK TRANSACTION;
            RETURN;
        END 
    END CATCH;  

    WHILE  @i <= @BatchSize
    BEGIN 
        BEGIN TRY
            BEGIN TRANSACTION

            SET @RowKey = (SELECT TOP 1 RowKey FROM dbo.MainJSONTable WHERE IsProcessed = 0 AND ProcessingFailed = 0 AND ProcessID = @ProcessID ORDER BY CreateDate ASC); 

            IF(@RowKey IS NULL)
            BEGIN
                RETURN;
            END

            BEGIN TRY
                EXEC dbo.DoStuffInChildProc @RowKey;
            END TRY
            BEGIN CATCH
                SET @msg = error_message();
                RAISERROR (@msg, 16, 1);
            END CATCH


            UPDATE dbo.MainJSONTable
                SET IsProcessed = 1 
            WHERE RowKey = @RowKey
            AND ProcessID = @ProcessID;

            COMMIT TRANSACTION;

        END TRY

        BEGIN CATCH

            IF (@@TRANCOUNT > 0)
            BEGIN
                ROLLBACK TRANSACTION;
            END 

            EXEC Logging.SetLogEntry @@PROCID, @msg;

            UPDATE dbo.MainJSONTable 
                SET ProcessingFailed = 1
            WHERE RowKey = @RowKey
            AND ProcessID = @ProcessID;

        END CATCH;  
        SET @i = @i + 1;
    END
END

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

CREATE PROCEDURE [dbo].[DoStuffInChildProc]
    @RowKey bigint
AS
BEGIN

    SET NOCOUNT ON;
    SET XACT_ABORT ON; --http://www.sommarskog.se/error_handling/Part1.html#jumpXACT_ABORT
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; --mandated by DBAs unless I provide good reason not to use it.


    BEGIN TRY

        --before this i get some dynamic sql ready.

        EXEC sp_executesql @FinalSQL;

    END TRY

    BEGIN CATCH
        IF @@trancount > 0 ROLLBACK TRANSACTION
        DECLARE @msg NVARCHAR(200) = CONCAT('[dbo].[DoStuffInChildProc] generated an error while processing RowKey ', @RowKey);
        EXEC Logging.SetLogEntry @@PROCID, @msg, @FinalSQL;
        RAISERROR(@msg, 16, 1);
        RETURN 1;
    END CATCH;  
END

Ошибка в моем журнале для родительской процедуры:

Transaction (Process ID 60) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

UPDATE

Это оператор создания таблицы, и я только что понял, что на данный момент в таблице нет никаких индексов, кроме кластеризованных на первичном ключе. Все дочерние процессы выбирают поле Result, которое содержит JSON для анализа. Должен ли я фиксировать дочерние транзакции, как только они закончатся, или просто переходить к родительскому процессу?

CREATE TABLE dbo.MainJSONTable (
    TableID bigint IDENTITY(1,1) NOT NULL,
    ParentTableFK bigint NULL,
    Result nvarchar(MAX),
    IsProcessed bit NOT NULL,
    ProcessingFailed big NOT NULL
    ProcessID
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...