2014-11-20 2 views
0

У меня есть длительная хранимая процедура в базе данных SQL-сервера. Я не хочу, чтобы он работал чаще, чем раз в десять минут.Как установить блокировку внутри хранимой процедуры?

Как только хранимая процедура запустилась, я хочу сохранить последний результат в таблице LatestResult против времени, и все вызовы процедуры возвращают этот результат в течение следующих десяти минут.

Это относительно просто, но мы обнаружили, что, поскольку процедура проверяет таблицу LatestResult и обновляет ее, большие пользовательские базы получают множество взаимоблокировок, когда два пользователя одновременно называют процедуру.

В ситуации с клиентской стороны/потоком я решил бы это с помощью блокировки, имея первый пользовательский замок функции, второй пользователь встречает блокировку, ожидая результата, первый пользователь заканчивает их вызов процедуры, обновляет таблицу LatestResult и разблокирует второго пользователя, который затем берет результат из таблицы LatestResult.

Есть ли способ выполнить этот тип блокировки в SQL Server?

EDIT:

Это в основном, как выглядит код без его ошибки проверки вызовов:

DECLARE @LastChecked AS DATETIME 
DECLARE @LastResult AS NUMERIC(18,2) 
SELECT TOP 1 @LastChecked = LastRunTime, @LastResult = LastResult FROM LastResult 

DECLARE @ReturnValue AS NUMERIC(18,2) 

IF DATEDIFF(n, @LastChecked, GetDate()) >= 10 OR NOT @LastResult = 0 
BEGIN 
    SELECT @ReturnValue = ABS(ISNULL(SUM(ISNULL(Amount,0)),0)) FROM Transactions WHERE ISNULL(DeletedFlag,0) = 0 GROUP BY GroupID ORDER BY ABS(ISNULL(SUM(ISNULL(Amount,0)),0)) 
     UPDATE LastResult SET LastRunTime = GETDATE(), LastResult = @ReturnValue 
     SELECT @ReturnValue 
    END 
ELSE 
BEGIN 
    SELECT @LastResult 
END 

Я не совсем уверен, что происходит с группировкой, но я нашел тестовая система, в которой время выполнения составляет около 4 секунд.

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

+0

Это похоже на продуманный план борьбы с медленным ходом хранимой процедуры.Сколько усилий было затрачено на оптимизацию SP? – DMason

+0

Он в основном запускает SUM() над большинством значений в столбце, но это большой столбец, и нам нужно значение довольно часто. Лично я бы просто вызвал SUM(), но, предположительно, процедура была введена предыдущим сотрудником по другой причине, кроме как быть неудобной и вызвать взаимоблокировки. Это не большое оправдание, но это все, что у меня есть, к сожалению. – Frosty840

+0

Можете ли вы разместить некоторый код tsql? – DMason

ответ

2

Это действительную возможность использовать блокировку приложения (см. sp_getapplock и sp_releaseapplock), так как это блокировка, выведенная по определению, которое вы определяете, а не по каким-либо конкретным строкам в данной таблице. Идея заключается в том, что вы создаете транзакцию, а затем создаете эту произвольную блокировку с индексом, а другие процессы будут ждать ввода этого фрагмента кода до тех пор, пока блокировка не будет выпущена. Это работает так же, как lock() на уровне приложения. Параметр @Resource является меткой произвольной «концепции». В более сложных ситуациях вы можете даже конкатенировать CustomerID или что-то там для более гранулированного контроля блокировки.

DECLARE @LastChecked DATETIME, 
     @LastResult NUMERIC(18,2); 
DECLARE @ReturnValue NUMERIC(18,2); 

BEGIN TRANSACTION; 
EXEC sp_getapplock @Resource = 'check_timing', @LockMode = 'Exclusive'; 

SELECT TOP 1 -- not sure if this helps the optimizer on a 1 row table, but seems ok 
     @LastChecked = LastRunTime, 
     @LastResult = LastResult 
FROM LastResult; 

IF (DATEDIFF(MINUTE, @LastChecked, GETDATE()) >= 10 OR @LastResult <> 0) 
BEGIN 
    SELECT @ReturnValue = ABS(ISNULL(SUM(ISNULL(Amount, 0)), 0)) 
    FROM Transactions 
    WHERE DeletedFlag = 0 
    OR  DeletedFlag IS NULL; 

    UPDATE LastResult 
    SET LastRunTime = GETDATE(), 
      LastResult = @ReturnValue; 
END; 
ELSE 
BEGIN 
    SET @ReturnValue = @LastResult; -- This is always 0 here 
END; 

SELECT @ReturnValue AS [ReturnValue]; 

EXEC sp_releaseapplock @Resource = 'check_timing'; 
COMMIT TRANSACTION; 

Вы должны управлять ошибки/ROLLBACK себя (как указано в документации, связанной MSDN), чтобы поместить в обычном TRY/CATCH. Но это позволяет вам управлять ситуацией.

Если есть какие-либо опасения относительно разногласий по этому процессу, не должно быть многого, поскольку поиск выполняется сразу после блокировки ресурса - это SELECT из таблицы с одной строкой, а затем оператор IF, который (в идеале) просто возвращает последнее известное значение, если 10-минутный таймер не истек. Следовательно, большинство вызовов должны обрабатываться довольно быстро.

Обращаем ваше внимание:sp_getapplock/sp_releaseapplock следует использовать экономно; Замки приложений могут определенно быть очень удобными (например, в таких случаях, как этот), но их следует использовать только в случае крайней необходимости.

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