2015-04-16 2 views
0

У меня относительно небольшой стол (на данный момент). Он работает как причудливая очередь. Задания, выполняющие каждую секунду /, задают эту таблицу для большей работы и всякий раз, когда работа завершается, они сообщают, что эта работа завершена.Как я могу избежать или минимизировать взаимоблокировки в этой ситуации?

Таблица имеет ~ 1000 записей или около того, записи и долгосрочные, мы надеемся, имеют 100k + строки Каждая запись означает задание, которое необходимо выполнить один раз в минуту. Таблица размещена в SQL Azure (S2 plan)

Job Starter выполняет хранимую процедуру, запрашивающую работу из этой таблицы. В принципе, proc просматривает таблицу, видит, какие задачи не выполняются и просрочены, отмечает их как «находящихся в процессе» и возвращает их на стартер задания.

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

ПРОБЛЕМА: я получаю тупики тихо часто при запросе этой таблицы для большей работы или попытке отметить завершенные записи. Похоже, подсказка ROWLOCK не работает. Мне нужна структура индексирования в этой таблице?

Вот хранимая процедура, которая извлекает записи (как правило, до 20, в то время, регулируется @count параметром

CREATE PROCEDURE [dbo].[sp_GetScheduledItems] 
@activity NVARCHAR (50), @count INT, @timeout INT=300, @dataCenter NVARCHAR (50) 
AS 
BEGIN 
SET NOCOUNT ON; 
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 

DECLARE @batchId uniqueidentifier 
SELECT @batchId = NEWID() 

DECLARE @result int; 
DECLARE @process nvarchar(255); 

    BEGIN TRAN 
    -- Update rows 
    UPDATE Schedule 
    WITH (ROWLOCK) 
    SET 
    LastBatchId = @batchId, 
    LastStartedProcessingId = NEWID(), 
    LastStartedProcessingTime = GETUTCDATE() 
    WHERE 
    ActivityType = @activity AND 
    IsEnabled = 1 AND 
    ItemId IN (
    SELECT TOP (@count) ItemId 
    FROM Schedule 
    WHERE 
     (LastStartedProcessingId = LastCompletedProcessingId OR LastCompletedProcessingId IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > @timeout) AND 
     IsEnabled = 1 AND ActivityType = @activity AND DataCenter = @dataCenter AND 
     (LastStartedProcessingTime IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > Frequency) 
    ORDER BY (DATEDIFF(SECOND, ISNULL(LastStartedProcessingTime, '1/1/2000'), GETUTCDATE()) - Frequency) DESC 
    ) 

    COMMIT TRAN 

    -- Return the updated rows 
    SELECT ItemId, ParentItemId, ItemName, ParentItemName, DataCenter, LastStartedProcessingId, Frequency, LastProcessTime, ActivityType 
    FROM Schedule 
    WHERE LastBatchId = @batchId 

END 
GO 

Вот хранимая процедура, которая помечает элементы как завершенные (он делает это один-на -a-времени)

CREATE PROCEDURE [dbo].[sp_CompleteScheduledItem] 
@activity NVARCHAR (50), @itemId UNIQUEIDENTIFIER, @processingId UNIQUEIDENTIFIER, @status NVARCHAR (50), @lastProcessTime DATETIME, @dataCenter NVARCHAR (50) 
AS 
BEGIN 
-- SET NOCOUNT ON added to prevent extra result sets from 
-- interfering with SELECT statements. 
SET NOCOUNT ON; 
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 

UPDATE Schedule WITH (ROWLOCK) 
SET 
    LastCompletedProcessingId = LastStartedProcessingId, 
    LastCompletedProcessingTime = GETUTCDATE(), 
    LastCompletedStatus = @status, 
    LastProcessTime = @lastProcessTime 
WHERE 
    ItemId = @itemId AND 
    LastStartedProcessingId = @processingId AND 
    DataCenter = @dataCenter AND 
    ActivityType = @activity 
END 
GO 

Вот сама таблица

CREATE TABLE [dbo].[Schedule](
    [ItemId] [uniqueidentifier] NOT NULL, 
    [ParentItemId] [uniqueidentifier] NOT NULL, 
    [ActivityType] [nvarchar](50) NOT NULL, 
    [Frequency] [int] NOT NULL, 
    [LastBatchId] [uniqueidentifier] NULL, 
    [LastStartedProcessingId] [uniqueidentifier] NULL, 
    [LastStartedProcessingTime] [datetime] NULL, 
    [LastCompletedProcessingId] [uniqueidentifier] NULL, 
    [LastCompletedProcessingTime] [datetime] NULL, 
    [LastCompletedStatus] [nvarchar](50) NULL, 
    [IsEnabled] [bit] NOT NULL, 
    [LastProcessTime] [datetime] NULL, 
    [DataCenter] [nvarchar](50) NOT NULL, 
    [ItemName] [nvarchar](255) NOT NULL, 
    [ParentItemName] [nvarchar](255) NOT NULL, 
CONSTRAINT [PK_Schedule] PRIMARY KEY CLUSTERED 
(
    [DataCenter] ASC, 
    [ItemId] ASC, 
    [ActivityType] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) 
) 
+0

По крайней мере, три различных соображений. 1. Наличие отсутствия индексов может влиять на поведение ROWLOCK (если есть индекс, индекс может быть заблокирован, а не строка). 2. Уровень изоляции транзакции может повлиять на результат. 3. Возможно, вы захотите использовать UPDLOCK, а не ROWLOCK (искать вокруг, есть ситуации, когда, похоже, это помогает избежать тупиковой ситуации - неожиданно так, насколько мне известно). – DWright

ответ

2

Это хороший квест ion :-) Как обычно, вы можете делать много вещей, но в вашем случае я думаю, что мы можем немного упростить ваш запрос. Обратите внимание, что приведенное ниже предложение не использует уровень изоляции SERIALIZABLE, который в вашем случае, по всей вероятности, вызывает блокировки на уровне таблицы, чтобы предотвратить появление фантомного чтения (а также делает весь доступ на запись к вашей таблице, ну, сериализован. на самом деле нужно указать BEGIN & COMMIT TRAN, поскольку вы публикуете только один оператор в своей транзакции (хотя это не повредит ни в вашем случае). В этом примере мы используем тот факт, что мы можем фактически выпустить ваше обновление непосредственно против подзапроса (в данном случае в виде КТР), и мы также можем удалить последнюю ВЫБРАТЬ, как мы можем вернуть результирующий набор непосредственно из оператора UPDATE.

НТН,

-Tobias

SQL Server Team может применить

CREATE PROCEDURE [dbo].[sp_GetScheduledItems] 
@activity NVARCHAR (50), @count INT, @timeout INT=300, @dataCenter NVARCHAR (50) 
AS 
BEGIN 
    SET NOCOUNT ON; 
    DECLARE @batchId uniqueidentifier 

    SELECT @batchId = NEWID() 
    DECLARE @result int; 
    DECLARE @process nvarchar(255); 

    -- Update rows 
    WITH a AS (
    SELECT TOP (@count) 
     * 
    FROM Schedule 
    WHERE 
    (LastStartedProcessingId = LastCompletedProcessingId OR LastCompletedProcessingId IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > @timeout) AND 
    IsEnabled = 1 AND ActivityType = @activity AND DataCenter = @dataCenter AND 
    (LastStartedProcessingTime IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > Frequency) 
    ORDER BY (DATEDIFF(SECOND, ISNULL(LastStartedProcessingTime, '1/1/2000'), GETUTCDATE()) - Frequency) DESC 
) 
    UPDATE a SET 
    LastBatchId = @batchId, 
    LastStartedProcessingId = NEWID(), 
    LastStartedProcessingTime = GETUTCDATE() 
    OUTPUT INSERTED.ItemId, INSERTED.ParentItemId, INSERTED.ItemName, INSERTED.ParentItemName, INSERTED.DataCenter, INSERTED.LastStartedProcessingId, INSERTED.Frequency, INSERTED.LastProcessTime, INSERTED.ActivityType 

END 
+0

Спасибо! Я просто видел подобное предложение здесь http://rusanu.com/2010/03/26/using-tables-as-queues/ Нужно ли мне какое-либо специальное индексирование на столе? – Igorek

+1

Чтобы избежать блокировки в этом случае, вы можете добавить индекс в столбцы, которые вы фильтруете, то есть IsEnabledm ActivityType и DataCenter.Вы также можете поменять порядок ActivityType и ItemID в своей PK. Я бы рекомендовал вам немного почитать о индексации в SQL Server, вы найдете массу полезной информации :-) Вы можете начать здесь: https://msdn.microsoft.com/en-us/library/ms175049 .aspx – user3657526

+0

Спасибо, Тобиас. Только улучшения CTE помогли/ton /. Больше нет взаимоблокировок, и все обрабатывается очень плавно. Оцените рекомендации по индексированию, это поможет, когда таблица станет больше. – Igorek

Смежные вопросы