한 번에 한 사람이 SQL 저장 프로 시저를 실행하도록 제한하려면 어떻게합니까?


12

기본적으로 하나의 테이블에서 값을 선택하고 다른 일종의 아카이브에 삽입하는 저장 프로 시저가 있습니다. 여러 사람이 동시에 그 일을하는 것을 피하고 싶습니다.

이 절차가 실행되는 동안 다른 사람이 시작하기를 원하지 않지만 직렬화를 원하지 않습니다. 다른 사람이 절차를 완료 한 후에 절차를 실행하지 않습니다.

내가 원하는 것은 절차를 실행하는 동안 다른 사람이 오류를 시작하려고 시도하는 것입니다.

sp_getapplock을 사용하여 시도했지만 사람이 절차를 실행하지 못하게 완전히 막을 수는 없습니다.

또한 sys.dm_exec_requests로 절차를 찾고 절차를 차단하려고 시도했지만 이것이 작동하는 동안 일부 서버에서는 sys.dm_exec_sql_text (sql_handle)을 실행할 권한이 없기 때문에 최적이 아니라고 생각합니다.

이 작업을 수행하는 가장 좋은 방법은 무엇입니까?


3
한 걸음 물러서서 절차가 수행되는 작업에 대해 더 많은 정보를 제공 할 수 있고 여러 사람이 동시에 실행하지 않는 이유는 무엇입니까? 이 요구 사항을 제거하는 코딩 기술이 있거나 사물을 처리하기 위해 구현할 수있는 일종의 큐가있을 수 있습니다.
AMtwo

답변:


15

@ Tibor-Karaszi의 답변에 추가하기 위해 잠금 시간 초과를 설정해도 실제로 오류가 발생하지 않습니다 (문서에 대해 PR을 제출했습니다). sp_getapplock은 -1 만 반환하므로 반환 값을 확인해야합니다. 이렇게 이렇게 :

create or alter procedure there_can_be_only_one 
as
begin
begin transaction

  declare @rv int
  exec @rv = sp_getapplock 'only_one','exclusive','Transaction',0
  if @rv < 0
   begin
      throw 50001, 'There is already an instance of this procedure running.', 10
   end

  --do stuff
  waitfor delay '00:00:20'


commit transaction
end

8

proc의 시작 부분에 sp_getapplock을 사용하고 lock timeout을 매우 낮은 값으로 설정하십시오. 이렇게하면 차단되었을 때 오류가 발생합니다.


7

다른 옵션은 프로 시저에 대한 액세스를 제어하기 위해 테이블을 작성하는 것입니다. 아래 예는 가능한 테이블과이를 사용할 수있는 절차를 보여줍니다.

CREATE TABLE dbo.ProcedureLock
    (
    ProcedureLockID INT NOT NULL IDENTITY(1,1)
    , ProcedureName SYSNAME NOT NULL
    , IsLocked BIT NOT NULL CONSTRAINT DF_ProcedureLock_IsLocked DEFAULT (0)
    , UserSPID INT NULL
    , DateLockTaken DATETIME2(7) NULL
    , DateLockExpires DATETIME2(7) NULL
    , CONSTRAINT PK_ProcedureLock PRIMARY KEY CLUSTERED (ProcedureLockID)
    )

CREATE UNIQUE NONCLUSTERED INDEX IDXUQ_ProcedureLock_ProcedureName
    ON dbo.ProcedureLock (ProcedureName)

INSERT INTO dbo.ProcedureLock
    (ProcedureName, IsLocked)
VALUES ('dbo.DoSomeWork', 0)

GO

CREATE PROCEDURE dbo.DoSomeWork
AS
BEGIN

    /** Take Lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 1
        , UserSPID = @@SPID
        , DateLockTaken = SYSDATETIME()
        , DateLockExpires = DATEADD(MINUTE, 10, SYSDATETIME())
    WHERE ProcedureName = 'dbo.DoSomeWork'
        AND (IsLocked = 0
            OR (IsLocked = 1 AND DateLockExpires < SYSDATETIME())
            )

    IF COALESCE(@@ROWCOUNT, 0) = 0
    BEGIN
        ;THROW 50000, 'This procedure can only be run one at a time, please wait', 1;
    END

    /** DO WHATEVER NEEDS TO BE DONE */

    /** Release the lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 0
        , UserSPID = NULL
        , DateLockTaken = NULL
        , DateLockExpires = NULL
    WHERE ProcedureName = 'dbo.DoSomeWork'

END

1
이것은 질문을 읽은 후 즉시 생각했던 것과 매우 유사하거나 어쩌면 동일하지만, 어떻게 해결할지 확실하지 않아서 답을 볼 수 없다는 생각에 문제가 있습니다. 어느 한 쪽. 내 관심사는 "무엇을해야합니까?"부분에서 어떤 일이 발생하면 어떻게됩니까? 이 경우 어떻게 IsLocked상태를 0으로 재설정 하시겠습니까? 나는 또한 당신의 사용에 대해 궁금 COALESCE합니다. ? @@ROWCOUNT와 같은 명령문 이후에 널이 될 수 있습니다 UPDATE. 마지막으로, 사소한 이쑤시개, 왜 THROW특정한 경우에 문장 앞에 세미콜론을 넣 습니까?
Andriy M

잠금 만료는이를 처리하는 한 가지 방법입니다. 합리적인 시간으로 설정해야합니다. 예제에서 10 분으로 설정했습니다. 원하는 경우 try / catch 블록에 작업 로직을 캡슐화하고 캐치에서 잠금을 해제 할 수 있습니다. COALESCE를 습관적으로 사용하지만 @@ ROWCOUNT는 NULL이 될 수 없습니다. 주요 세미콜론은 Visual Studio 데이터베이스 프로젝트 작업에서 비롯된 것이 아니라고 불평합니다. 어느 쪽도 해를 끼치 지 않습니다.
Jonathan Fite

-1

잘못된 방법으로 문제를 해결하려고한다고 생각합니다. 원하는 것은 데이터베이스 일관성을 최대한 보호하는 것입니다. 두 사람이 동시에 저장 프로 시저를 실행하면 데이터베이스 일관성이 위반 될 수 있습니다.

다양한 종류의 데이터베이스 불일치로부터 보호하기 위해 SQL 표준에는 다음과 같은 네 가지 트랜잭션 격리 수준이 있습니다.

  • 기본적으로 트랜잭션의 가치가 떨어지고 다른 트랜잭션이 더티 데이터를 보는 경우 커밋되지 않은 상태읽으십시오 . 이것을 사용하지 마십시오!
  • READ 커밋 트랜잭션이 커밋 된 데이터를 볼 경우지만, 두 트랜잭션이 서로의 발가락을 통해 단계 수 불일치가있을 수 있습니다
  • 반복 불가능한 읽기 중 하나의 불일치, 반복 불가능한 읽기가 해결되는 반복 읽기
  • SERIALIZABLE 은 트랜잭션을 실행하면 실행 결과로 이어질 가상 순서가 있음을 보장합니다.

그러나 SQL 표준에는 이러한 데이터베이스 불일치에 대한 잠금 기반 접근 방식이 있으며 성능상의 이유로 많은 데이터베이스는 기본적으로 다음과 같은 수준의 스냅 샷 격리 기반 접근 방식을 취합니다.

  • 잠금 기반 데이터베이스와 동일한 READ COMMITTED
  • SNAPSHOT ISOLATION 데이터베이스에서 모든 데이터의 스냅 샷을보고 다른 트랜잭션으로 업데이트 된 행을 업데이트하려고하면 취소되지만 아직 알려진 예외가 있습니다.
  • SERIALIZABLE 은 잠금 기반 데이터베이스와 동일하지만 이번에는 잠금을 수행하는 것이 아니라 직렬화 위반이 없는지 확인하고 이러한 위반이 감지되면 트랜잭션을 취소하여 다른 방식으로 구현됩니다.

이러한 스냅 샷 격리 기반 데이터베이스의 트랜잭션 취소는 걱정스러운 것처럼 들릴 수 있지만 교착 상태로 인해 모든 단일 데이터베이스가 트랜잭션을 취소하므로 합리적인 응용 프로그램은 트랜잭션을 다시 시도 할 수 있어야합니다.

원하는 것은 직렬화 가능 분리 레벨입니다. 트랜잭션이 서로 독립적으로 실행되면 양호한 상태가되며 트랜잭션의 병렬 실행도 양호한 상태가됩니다. 다행스럽게도 Michael Cahill은 박사 학위 논문 에서 약간의 노력으로 스냅 샷 격리 데이터베이스가 SERIALIZABLE 격리 수준을 어떻게 지원할 수 있는지 알아 냈습니다 .

스냅 샷 격리 데이터베이스에서 직렬화 가능 격리 레벨을 사용하는 경우 두 사람이 저장 프로 시저를 동시에 실행하려고 시도하고 서로 발가락을 밟으면 트랜잭션 중 하나가 취소됩니다.

이제 SQL Server 는 SERIALIZABLE 키워드 뒤에 마스킹 레이 딩 스냅 샷 격리 대신 SERIALIZABLE 격리 수준을 지원 합니까? 솔직히 말해서, 나는 그것을 알고있는 유일한 데이터베이스는 PostgreSQL입니다.

SQL Server에 특정 조언을 제공하지는 않았지만 PostgreSQL 사용자와 PostgreSQL로 전환을 고려할 수있는 다른 데이터베이스 사용자가 내 답변의 이점을 누릴 수 있기 때문에 여전히이 답변을 게시하고 있습니다. 또한 PostgreSQL로 전환 할 수없는 PostgreSQL 이외의 데이터베이스 사용자는 즐겨 사용하는 데이터베이스 공급 업체에 진정으로 SERIALIZABLE 격리 수준 을 제공하도록 압력을 줄 수 있습니다 .


다운 보트는 누군가 SQL Server에 SERIALIZABLE 격리 수준이 있는지 여부를 조사했지만 그렇지 않다는 것을 알았습니다.
juhist

-2

'실제'문제가 더 복잡 할 수 있음을 알고 있습니다.

그렇지 않은 경우 : 삽입 및 / 또는 업데이트 트리거로 아카이빙을 수행하면 해결하려는 문제를 피할 수 있습니다.

도움이
되길 바랍니다 -Chris C.


1
"즉시"란 무엇을 의미합니까? 무엇 직후? 삽입 후? 새 행이 도착하자마자 즉시 아카이브로 보내 집니까? 아니면 업데이트 후 의미 했습니까? 어떤 데이터 변경으로 인해 아카이브가 트리거됩니까? 아마도 어떤 시나리오를 제안 할 것인지에 대해 더 구체적이어야합니다.
Andriy M

특히 소스 테이블이 빈번하게 삽입되거나 테이블과 아카이브 사이의 트랜잭션 안전성이 값 비싼 잠금을 필요로하는 경우, 아카이브는 모든 인서트에서 수행 할 가치가없고 너무 비싸지 않을 수 있습니다.
underscore_d

@underscore_d 예, 너무 비싸거나 항상 필요한 것은 아닙니다. 이것이 바로 다음과 같은 진술로 대답을 시작한 이유입니다 the 'real' problem may be more complex. 그렇지 않은 경우 트리거가 좋은 솔루션입니다. 또한 사용자 지정 솔루션 대신 데이터베이스 기능이므로 테스트 및 유지 관리가 더 쉬울 것입니다.
J. Chris Compton

@AndriyM 나는 단어를 즉시 제거하고 트리거를 삽입 / 업데이트하는 참조로 대체했습니다. 혼란을 드려 죄송합니다.
J. Chris Compton

1
질문을 다시 읽었으며 혼란의 원인을 볼 수 있다고 생각합니다. 여기서 제안하는 것은 아카이빙보다 감사와 유사합니다. 내가 이해하는 것처럼 데이터를 보관한다는 것은 데이터를 한 테이블에서 다른 테이블로 이동하는 것을 의미합니다. 그러나 OP가 프로 시저 기능을 "일종의 아카이빙"으로 요약했지만 데이터가 소스에서 제거되고 소스에서 선택되어 대상에 삽입 될 것이라고 만 말한 적이 없습니다. 따라서 OP가 데이터를 이동 하지 않고 복사 해야한다고 가정합니다. 이 경우 트리거를 사용하는 것이 좋습니다.
Andriy M
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.