교착 상태의 주요 원인은 무엇이며 예방할 수 있습니까?


55

최근 ASP.NET 응용 프로그램 중 하나에 데이터베이스 교착 상태 오류가 표시되어 오류를 확인하고 수정하라는 요청을 받았습니다. 교착 상태의 원인은 커서 내에서 테이블을 엄격하게 업데이트하는 저장 프로 시저였습니다.

이 오류를 처음으로보고 효과적으로 추적하고 수정하는 방법을 몰랐습니다. 내가 아는 가능한 모든 방법을 시도하고 마침내 업데이트되는 테이블에 기본 키가 없음을 발견했습니다! 운 좋게도 그것은 열이었다.

나중에 배포 엉망으로 데이터베이스를 스크립팅 한 개발자를 찾았습니다. 기본 키를 추가했는데 문제가 해결되었습니다.

나는 행복을 느꼈고 내 프로젝트로 돌아 왔고 그 교착 상태의 이유를 알아 내기 위해 약간의 연구를했습니다 ...

교착 상태의 원인은 순환 대기 조건 인 것 같습니다. 기본 키보다 기본 키없이 업데이트가 더 오래 걸립니다.

잘 정의 된 결론이 아니라는 것을 알고 있으므로 여기에 게시하고 있습니다 ...

  • 누락 된 기본 키가 문제입니까?
  • (상대 제외, 보류 및 대기, 선점 및 순환 대기 없음) 이외의 교착 상태를 유발하는 다른 조건이 있습니까?
  • 교착 상태를 방지하고 추적하려면 어떻게합니까?

2
내가 본 교착 상태의 IME 대다수 (모두?)는 순환 대기 (주로 과도한 트리거 사용)로 인해 발생합니다.
Sathyajith Bhat

원형도는 교착 상태의 필수 조건 중 하나입니다. 모든 세션이 동일한 순서로 잠금을 획득하면 교착 상태를 피할 수 있습니다.
Peter G.

답변:


38

교착 상태 추적은 두 가지 중 더 쉽습니다.

기본적으로 교착 상태는 오류 로그에 기록되지 않습니다. 추적 플래그 1204 및 3605를 사용하여 SQL이 교착 상태를 오류 로그에 기록하게 할 수 있습니다.

교착 상태 정보를 SQL Server 오류 로그에 작성하십시오. DBCC TRACEON (-1, 1204, 3605)

끄기 : DBCC TRACEOFF (-1, 1204, 3605)

추적 플래그 1204 및 켜져있을 때 얻을 수있는 출력에 대한 설명은 "교착 상태 문제 해결"을 참조하십시오. https://msdn.microsoft.com/en-us/library/ms178104.aspx

예방은 더 어렵습니다. 본질적으로 다음을주의해야합니다.

코드 블록 1은 순서대로 리소스 A를 잠근 다음 리소스 B를 잠급니다.

코드 블록 2는 순서대로 리소스 B를 잠근 다음 리소스 A를 잠급니다.

교착 상태가 발생할 수있는 전형적인 조건입니다. 두 자원의 잠금이 원자 적이 지 않으면 코드 블록 1이 A를 잠그고 선점 할 수 있으며 A가 처리 시간을 다시 받기 전에 코드 블록 2가 B를 잠급니다. 이제 교착 상태가 발생했습니다.

이 상태를 방지하기 위해 다음과 같은 작업을 수행 할 수 있습니다

코드 블록 A (의사 코드)

Lock Shared Resource Z
    Lock Resource A
    Lock Resource B
Unlock Shared Resource Z
...

코드 블록 B (의사 코드)

Lock Shared Resource Z
    Lock Resource B
    Lock Resource A
Unlock Shared Resource Z
...

완료되면 A와 B를 잠금 해제하는 것을 잊지 마십시오.

이것은 코드 블록 A와 코드 블록 B 사이의 교착 상태를 방지합니다

데이터베이스 관점에서 볼 때 잠금은 데이터베이스 자체, 즉 데이터를 업데이트 할 때 행 / 테이블 잠금에 의해 처리되므로 이러한 상황을 방지하는 방법을 잘 모르겠습니다. 가장 많이 발생하는 문제는 커서 내부에서 커서를 보는 위치입니다. 커서는 악명 높은 것으로 악명 높으며, 가능한 경우 피하십시오.


코드 블록 B에서 리소스 B보다 리소스 A를 잠그려고 했습니까? 서면으로, 이것은 당신이 이전 의견에서 언급했듯이 교착 상태를 일으킬 것입니다. 잠금 순서를 보장하기 위해 처음에 더미 쿼리가 필요한 경우에도 가능한 한 항상 동일한 순서로 리소스를 잠그려고합니다.
제라드 ONeill

23

교착 상태에 대해 읽고 배우는 가장 좋아하는 기사는 다음과 같습니다. Simple Talk-교착 상태 추적SQL Server Central-프로파일 러를 사용하여 교착 상태 해결 . 상황을 처리하는 방법에 대한 샘플과 조언을 제공합니다.

요컨대, 현재 문제를 해결하기 위해 관련된 트랜잭션을 더 짧게 만들고 불필요한 부분을 제거하고 객체 사용 순서를 처리하며 실제로 필요한 격리 수준을 확인하고 불필요한 읽기는하지 않습니다. 데이터...

그러나 기사를 더 잘 읽으면 조언이 더 좋을 것입니다.


16

데이터베이스가 전체 테이블이 아닌 개별 레코드를 잠글 수있게하므로 색인 작성을 추가하여 교착 상태를 해결할 수있는 경우가 있기 때문에 경합 및 상황이 발생할 가능성이 줄어 듭니다.

예를 들어 InnoDB에서 :

명령문에 적합한 인덱스가없고 MySQL이 명령문을 처리하기 위해 전체 테이블을 스캔해야하는 경우 테이블의 모든 행이 잠기고 다른 사용자가 테이블에 대한 모든 삽입을 차단합니다. 쿼리가 불필요하게 많은 행을 스캔하지 않도록 올바른 인덱스를 작성하는 것이 중요합니다.

또 다른 일반적인 해결책은 필요하지 않을 때 트랜잭션 일관성을 해제하거나 통계를 계산하기 위해 장기간 실행되는 작업과 같이 격리 수준을 변경 하는 것입니다. 일반적으로 충분한 대답은 충분합니다. 그들이 당신 아래에서 변화함에 따라. 그리고 완료하는 데 30 분이 걸리는 경우 해당 테이블의 다른 모든 트랜잭션을 중지하고 싶지 않습니다.

...

그것들을 추적하는 것은 사용중인 데이터베이스 소프트웨어에 달려 있습니다.


다운 보팅시 의견을 제공하는 것이 일반적입니다. 이것은 유효한 답변입니다. 테이블 잠금으로 업그레이드하고 영원히 가져가는 select 문은 반드시 교착 상태를 유발할 수 있습니다.
BlackICE

1
인덱스가 클러스터되지 않은 경우 MS SQLServer는 예기치 않은 잠금 동작을 제공 할 수도 있습니다. 행 수준 잠금을 사용하는 방향을 자동으로 무시하고 페이지 수준 잠금을 수행합니다. 그런 다음 페이지에서 교착 상태를 기다릴 수 있습니다.
Jay

7

커서로 개발하기 만하면됩니다. 실제로 정말 나쁘다. 전체 테이블을 잠근 다음 행을 하나씩 처리합니다.

while 루프를 사용하여 커서 방식으로 행을 통과하는 것이 가장 좋습니다.

while 루프에서는 루프의 각 행에 대해 선택이 수행되고 잠금은 한 번에 한 행에서만 발생합니다. 테이블의 나머지 데이터는 쿼리 할 수 ​​있으므로 교착 상태가 발생할 가능성이 줄어 듭니다.

게다가 더 빠릅니다. 어쨌든 커서가 왜 있는지 궁금해합니다.

이러한 종류의 구조의 예는 다음과 같습니다.

DECLARE @LastID INT = (SELECT MAX(ID) FROM Tbl)
DECLARE @ID     INT = (SELECT MIN(ID) FROM Tbl)
WHILE @ID <= @LastID
    BEGIN
    IF EXISTS (SELECT * FROM Tbl WHERE ID = @ID)
        BEGIN
        -- Do something to this row of the table
        END

    SET @ID += 1  -- Don't forget this part!
    END

ID 필드가 희소 한 경우 별도의 ID 목록을 가져 와서 반복 할 수 있습니다.

DECLARE @IDs TABLE
    (
    Seq INT NOT NULL IDENTITY PRIMARY KEY,
    ID  INT NOT NULL
    )
INSERT INTO @IDs (ID)
    SELECT ID
    FROM Tbl
    WHERE 1=1  -- Criteria here

DECLARE @Rec     INT = 1
DECLARE @NumRecs INT = (SELECT MAX(Seq) FROM @IDs)
DECLARE @ID      INT
WHILE @Rec <= @NumRecs
    BEGIN
    SET @ID = (SELECT ID FROM @IDs WHERE Seq = @Seq)

    -- Do something to this row of the table

    SET @Seq += 1  -- Don't forget this part!
    END

6

기본 키가 없어도 문제 가 되지 않습니다 . 적어도 그 자체로. 첫째, 인덱스를 가지기 위해 프라이 머리가 필요하지 않습니다. 둘째, 테이블 스캔을 수행하더라도 (특정 쿼리가 인덱스를 사용하지 않는 경우 발생해야 함) 테이블 잠금 자체가 교착 상태를 일으키지 않습니다. 쓰기 프로세스는 읽기를 기다릴 것이며 읽기 프로세스는 쓰기를 기다리십시오. 물론 읽기는 서로를 기다릴 필요가 없습니다.

다른 답변에 덧붙여서, 반복 가능한 읽기와 직렬화가 트랜잭션의 끝까지 '읽기'잠금을 유지하기 때문에 트랜잭션 격리 수준이 중요합니다. 리소스를 잠그더라도 교착 상태가 발생하지 않습니다. 잠금 상태를 유지합니다. 쓰기 작업은 항상 트랜잭션이 끝날 때까지 리소스를 잠급니다.

내가 가장 좋아하는 잠금 방지 전략은 '스냅 샷'기능을 사용하는 것입니다. 커밋 된 스냅 샷 읽기 기능은 읽기에서 잠금을 사용하지 않음을 의미합니다! '읽기 커밋'보다 더 많은 제어가 필요한 경우 '스냅 샷 격리 수준'기능이 있습니다. 이것은 다른 플레이어를 차단하지 않으면 서 일련의 (여기서는 MS 용어 사용) 트랜잭션이 발생하도록합니다.

마지막으로, 업데이트 잠금을 사용하여 한 클래스의 교착 상태를 방지 할 수 있습니다. 읽기 (HOLD 또는 반복 가능한 읽기 사용)를 읽고 유지 한 상태에서 다른 프로세스가 동일한 작업을 수행하면 둘 다 동일한 레코드를 업데이트하려고하면 교착 상태가 발생합니다. 그러나 둘 다 업데이트 잠금을 요청하면 두 번째 프로세스는 첫 번째 프로세스를 기다리면서 실제로 프로세스가 작성 될 때까지 다른 프로세스가 공유 잠금을 사용하여 데이터를 읽을 수 있도록합니다. 프로세스 중 하나가 여전히 공유 HOLD 잠금을 요청하는 경우에는 작동하지 않습니다.


-2

SQL Server에서 커서가 느리면 커서의 소스 데이터를 Temp 테이블로 가져 와서 커서를 실행하여 커서의 교착 상태를 피할 수 있습니다. 이렇게하면 커서가 실제 데이터 테이블을 잠그지 않고 커서 내부에서 수행되는 업데이트 또는 삽입에 대한 유일한 잠금은 삽입 / 업데이트 기간 동안 만 유지되며 커서 기간 동안은 유지되지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.