Мне нравится sp_getapplock
, и я сам использую этот метод в нескольких местах из-за его гибкости и полного контроля над логикой блокировки c и временем ожидания.
Единственная проблема, которую я вижу, состоит в том, что в вашем случае параллельные процессы не все равны.
У вас есть SP1, который перемещает данные из промежуточной таблицы в основную таблицу. Ваша система никогда не пытается запустить несколько экземпляров этого SP.
Другой SP2, который вставляет данные в промежуточную таблицу , может запускаться несколько раз одновременно, и это нормально.
Легко реализовать блокировку, которая предотвратит любой параллельный запуск любой комбинации SP1 или SP2. Другими словами, легко, если логика блокировки c одинакова для SP1 и SP2, и они рассматриваются как равные. Но тогда вы не можете иметь несколько экземпляров SP2, работающих одновременно.
Не очевидно, как реализовать блокировку, которая предотвратит одновременный запуск SP1 и SP2, одновременно позволяя нескольким экземплярам SP2 работать одновременно.
Существует другой подход, который не пытается предотвратить одновременный запуск SP, но охватывает и предполагает, что возможны одновременные запуски.
Один из способов сделать это - добавить IDENTITY
столбец к промежуточной таблице. Или автоматическое заполнение даты и времени, если вы можете гарантировать, что оно уникально и никогда не уменьшается, что может быть сложно. Или rowversion
столбец.
Лог c внутри SP2, который вставляет данные в промежуточную таблицу, не изменяется.
Лог c внутри СП1 для перемещения данных из промежуточной таблицы в основную таблицу необходимо использовать эти значения идентификаторов.
Сначала прочитайте текущее максимальное значение идентификатора из промежуточной таблицы и запомните его в переменной, скажем, @MaxID
. Все последующие операции SELECT, UPDATE и DELETE из промежуточной таблицы в этом SP1 должны включать фильтр WHERE ID <= @MaxID
.
. Это позволит гарантировать, что в случае появления новой строки, добавленной в промежуточную таблицу во время работы SP1, эта строка не будет обработана и будет оставаться в промежуточной таблице до следующего запуска SP1.
Недостаток этого подхода заключается в том, что вы не можете использовать TRUNCATE
, вам нужно использовать DELETE
с WHERE ID <= @MaxID
.
Если вы в порядке с несколькими экземплярами SP2, ожидающими друг друга (и SP1), то вы можете использовать sp_getapplock
, как показано ниже. У меня есть этот код в моей хранимой процедуре. Вы должны поместить эту логику c как в SP1, так и в SP2.
Я не буду звонить sp_releaseapplock
здесь явно, потому что для владельца блокировки установлено значение Transaction, и механизм освободит блокировка автоматически по окончании транзакции.
Вам не нужно помещать logry retry c в хранимую процедуру, это может быть внутри внешнего кода, который выполняет эти хранимые процедуры. В любом случае ваш код должен быть готов к повторной попытке.
CREATE PROCEDURE SP2 -- or SP1
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION;
BEGIN TRY
-- Maximum number of retries
DECLARE @VarCount int = 10;
WHILE (@VarCount > 0)
BEGIN
SET @VarCount = @VarCount - 1;
DECLARE @VarLockResult int;
EXEC @VarLockResult = sp_getapplock
@Resource = 'StagingTable_app_lock',
-- this resource name should be the same in SP1 and SP2
@LockMode = 'Exclusive',
@LockOwner = 'Transaction',
@LockTimeout = 60000,
-- I'd set this timeout to be about twice the time
-- you expect SP to run normally
@DbPrincipal = 'public';
IF @VarLockResult >= 0
BEGIN
-- Acquired the lock
-- for SP2
-- INSERT INTO StagingTable ...
-- for SP1
-- SELECT FROM StagingTable ...
-- TRUNCATE StagingTable ...
-- don't retry any more
BREAK;
END ELSE BEGIN
-- wait for 5 seconds and retry
WAITFOR DELAY '00:00:05';
END;
END;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
-- log error
END CATCH;
END
Этот код гарантирует, что в любой момент времени с промежуточной таблицей работает только одна процедура. Там нет параллелизма. Все остальные экземпляры будут ждать.
Очевидно, что если вы попытаетесь получить доступ к промежуточной таблице не через эти SP1 или SP2 (которые сначала пытаются получить блокировку), то такой доступ не будет заблокирован.