У меня есть хранимая процедура, которая вызывает дочерние хранимые процедуры для анализа 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
)