2014-11-20 2 views
0

SQL 서버 데이터베이스에서 장기 실행 저장 프로 시저가 있습니다. 10 분마다 한 번 이상 실행하는 것을 원하지 않습니다.저장 프로 시저 내부에서 잠금을 설정하려면 어떻게해야합니까?

일단 저장 프로 시저가 실행되면 시간에 대해 LatestResult 테이블에 최신 결과를 저장하고 프로 시저에 대한 모든 호출에서 다음 10 분 동안 결과를 반환해야합니다.

그 정도는 비교적 간단하지만 절차에서 LatestResult 테이블을 확인하고 업데이트하므로 두 사용자가 동시에 프로 시저를 호출 할 때 큰 사용자베이스에 여러 가지 교착 상태가 발생하는 것으로 나타났습니다.

클라이언트 측/스레딩 상황에서는 먼저 잠금을 사용하여 첫 번째 사용자가 함수를 잠그면 두 번째 사용자가 잠금을 만나고 결과를 기다리고 첫 번째 사용자가 프로 시저 호출을 완료하고, LatestResult 테이블을 업데이트하고 두 번째 사용자의 잠금을 해제 한 다음 두 번째 사용자는 LatestResult 테이블에서 결과를 가져옵니다.

SQL Server에서 이러한 종류의 잠금을 수행 할 수있는 방법이 있습니까?

편집 : 내가 찾은

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_getapplocksp_releaseapplock 참조)을 사용할 수있는 유효한 기회는 지정된 테이블의 특정 행이 아니라 정의한 개념에 따라 취해진 잠금이기 때문입니다. 이 아이디어는 트랜잭션을 생성 한 다음 인디시 파이어가있는 임의의 잠금을 생성하고 잠금이 해제 될 때까지 다른 프로세스가 해당 코드를 입력하기를 기다리는 것입니다. 이는 앱 계층에서 lock()처럼 작동합니다. @Resource 매개 변수는 임의의 "개념"의 레이블입니다. 보다 복잡한 상황에서는보다 세분화 된 잠금 제어를 위해 고객 ID 또는 그 안의 항목을 연결할 수도 있습니다.

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; 

당신은 너무 일반적인 TRY/CATCH에 넣어/오류 관리 (링크 MSDN 설명서에 명시된 바와 같이) 자신을 롤백해야합니다. 그러나이 방법으로 상황을 관리 할 수 ​​있습니다.

이 프로세스에서 경합과 관련하여 우려되는 사항이 있으면 리소스를 잠그고 바로 조회가 단일 행 테이블의 SELECT이고 그런 다음 (이상적으로) 그냥 반환하는 IF 문이어야합니다. 10 분 타이머가 경과하지 않은 경우 마지막으로 알려진 값입니다. 따라서 대부분의 호출은 다소 빠르게 처리되어야합니다.

참고 :sp_getapplock/sp_releaseapplock 아껴서 사용해야합니다; 응용 프로그램 잠금은 확실히 (이 경우와 같이) 매우 유용 할 수 있지만 절대적으로 필요한 경우에만 사용해야합니다.