У меня есть ресурс (например, «документ»), хранящийся в таблице SQL Server.
Пользователи должны иметь возможность получить блокировку уровня приложения (а не блокировку базы данных) для этого ресурса и удерживать эту блокировку потенциально навсегда.
Я разработал стратегию, чтобы справиться с этим, но я не уверен, что она правильная, и был бы признателен за совет.
В этом конкретном приложении количество пользователей и ресурсов будет очень низким, поэтому не будет большого количества споров. Но все равно было бы неплохо узнать наиболее подходящий или «правильный» способ справиться с этой ситуацией.
Таблица для записи блокировок достаточно проста:
CREATE TABLE [dbo].[ResourceLock]
(
[Id] [int] NOT NULL IDENTITY(1,1),
[ResourceId] [int] NOT NULL,
[UserId] [int] NOT NULL,
[Created] [datetime2](7) NOT NULL,
CONSTRAINT [PK_OrderLock] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
)
А затем запрос на попытку снять блокировку ресурса:
--Incoming Parameters
DECLARE @ResourceId INT;
DECLARE @UserId INT;
--Query Variables
DECLARE @LockId INT;
DECLARE @InsertedIds TABLE (Id INT);
BEGIN TRANSACTION;
SELECT @LockId = [ResourceLock].[Id]
--TABLOCKX the table so it can't be accessed until after the transaction is complete.
FROM [ResourceLock] WITH (TABLOCKX)
WHERE [ResourceLock].[ResourceId] = @ResourceId;
IF @LockId IS NULL
BEGIN
INSERT INTO [ResourceLock]
(
[ResourceId],
[UserId],
[Created]
)
OUTPUT inserted.Id INTO @InsertedIds(Id)
VALUES
(
@ResourceId,
@UserId,
GETUTCDATE()
);
SELECT @LockID = Id
FROM @InsertedIds;
END
SELECT [ResourceLock].*
FROM [ResourceLock]
WHERE [ResourceLock].[Id] = @LockId;
COMMIT TRANSACTION;
Сначала проверяется, существует ли блокировка ресурса. В рамках этой проверки SELECT
указывает TABLOCKX
, запрещающий другому экземпляру этого запроса или любому другому запросу доступ к этой таблице, пока эта транзакция не будет завершена.
Если не существует существующей блокировки, создается новая блокировка для ресурса для пользователя.
Наконец, он возвращает существующую или вновь созданную блокировку.
Затем приложение просто сравнивает запрашивающего пользователя с пользователем, удерживающим блокировку, и сообщает пользователю, получил ли он блокировку, или блокировка удержана кем-то еще.
Когда другие запросы пытаются записать ресурс, они будут использовать такую проверку:
--Incoming Parameters
DECLARE @ResourceId INT;
DECLARE @UserId INT;
IF EXISTS
(
SELECT [ResourceLock].*
FROM [ResourceLock]
WHERE [ResourceLock].[ResourceId] = @ResourceId
AND [ResourceLock].[UserId] = @UserId
)
BEGIN
--UPDATE the resource
END
Мое главное беспокойство связано с использованием TABLOCKX
, так как это очень тяжелые руки. Это похоже на то, что может быть достигнуто с другим подходом, который был бы более эффективным, но я не смог найти ничего подобного при поиске.
Я думаю, что мои попытки поиска были затруднены тем фактом, что у меня здесь происходит два типа блокировки. Блокировка в SQL Server и блокировка ресурсов приложения.