[sql-server] SQL Server 2005에서 교착 상태 진단

Stack Overflow SQL Server 2005 데이터베이스에서 악성이지만 드문 교착 상태가 발생했습니다.

프로파일 러를 연결하고 교착 상태 문제 해결에 대한이 훌륭한 기사를 사용하여 추적 프로파일을 설정 하고 여러 예제를 캡처했습니다. 이상한 점은 교착 상태 쓰기가 항상 동일 하다는 것입니다 .

UPDATE [dbo].[Posts]
SET [AnswerCount] = @p1, [LastActivityDate] = @p2, [LastActivityUserId] = @p3
WHERE [Id] = @p0

다른 교착 상태 문은 다양하지만 일반적으로 posts 테이블 을 간단하고 간단하게 읽습니다 . 이것은 항상 교착 상태로 죽습니다. 여기에 예가 있습니다.

SELECT
[t0].[Id], [t0].[PostTypeId], [t0].[Score], [t0].[Views], [t0].[AnswerCount],
[t0].[AcceptedAnswerId], [t0].[IsLocked], [t0].[IsLockedEdit], [t0].[ParentId],
[t0].[CurrentRevisionId], [t0].[FirstRevisionId], [t0].[LockedReason],
[t0].[LastActivityDate], [t0].[LastActivityUserId]
FROM [dbo].[Posts] AS [t0]
WHERE [t0].[ParentId] = @p0

명확하게 말하면, 쓰기 / 쓰기 교착 상태가 아니라 읽기 / 쓰기입니다.

현재 LINQ와 매개 변수화 된 SQL 쿼리가 혼합되어 있습니다. with (nolock)모든 SQL 쿼리에 추가 했습니다. 이것은 일부 도움이되었을 수 있습니다. 또한 어제 수정 한 (매우) 잘못 작성된 배지 쿼리가 하나있었습니다. 매번 실행하는 데 20 초 이상이 걸리고 그 위에 매분 실행되었습니다. 이것이 잠금 문제의 원인이되기를 바랬습니다!

불행히도 약 2 시간 전에 또 다른 교착 상태 오류가 발생했습니다. 똑같은 증상, 똑같은 범인이 쓴다.

정말 이상한 점은 위에서 본 잠금 쓰기 SQL 문이 매우 특정한 코드 경로의 일부라는 것입니다. 그것은 것 에만 새 응답이 질문에 추가 될 때 실행 – 그것은 새 응답 수와 마지막 날짜 / 사용자와 부모의 질문을 업데이트합니다. 이것은 분명히 우리가 수행하는 엄청난 수의 읽기에 비해 그렇게 일반적이지 않습니다! 내가 말할 수있는 한, 우리는 앱 어디에서나 엄청난 수의 쓰기를하고 있지 않습니다.

NOLOCK이 일종의 거대한 망치라는 것을 알고 있지만 여기서 실행하는 대부분의 쿼리는 정확할 필요가 없습니다. 사용자 프로필이 몇 초가 지난 경우에도 신경 쓰시겠습니까?

Linq와 함께 NOLOCK을 사용하는 것은 Scott Hanselman이 여기에서 설명 하는 것처럼 조금 더 어렵습니다 .

우리는 사용의 아이디어를 유혹하고 있습니다

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

모든 LINQ 쿼리가이 세트를 갖도록 기본 데이터베이스 컨텍스트에서 그것 없이는 우리가 만드는 모든 LINQ 호출 (대부분의 간단한 읽기 호출)을 3-4 줄 트랜잭션 코드 블록으로 래핑해야합니다.

SQL 2005의 사소한 읽기가 쓰기에 교착 상태가 될 수 있다는 사실에 약간 실망한 것 같습니다. 쓰기 / 쓰기 교착 상태가 큰 문제라는 것을 알 수 있지만 읽습니까? 우리는 여기서 은행 사이트를 운영하지 않으며 매번 완벽한 정확성이 필요하지 않습니다.

아이디어? 생각?


모든 작업에 대해 새 LINQ to SQL DataContext 개체를 인스턴스화하고 있습니까? 아니면 모든 호출에 대해 동일한 정적 컨텍스트를 공유하고 있습니까?

Jeremy, 우리는 대부분의 경우 기본 컨트롤러에서 하나의 정적 데이터 컨텍스트를 공유합니다.

private DBContext _db;
/// <summary>
/// Gets the DataContext to be used by a Request's controllers.
/// </summary>
public DBContext DB
{
    get
    {
        if (_db == null)
        {
            _db = new DBContext() { SessionName = GetType().Name };
            //_db.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
        }
        return _db;
    }
}

모든 컨트롤러에 대해 또는 페이지별로 또는 .. 더 자주 새 컨텍스트를 만드는 것이 좋습니까?



답변

MSDN에 따르면 :

http://msdn.microsoft.com/en-us/library/ms191242.aspx

READ COMMITTED SNAPSHOT 또는 ALLOW SNAPSHOT ISOLATION 데이터베이스 옵션이 ON이면 데이터베이스에서 수행 된 모든 데이터 수정에 대해 논리적 복사본 (버전)이 유지됩니다. 특정 트랜잭션에 의해 행이 수정 될 때마다 데이터베이스 엔진 인스턴스는 이전에 커밋 된 행 이미지 버전을 tempdb에 저장합니다. 각 버전은 변경 한 트랜잭션의 트랜잭션 시퀀스 번호로 표시됩니다. 수정 된 행의 버전은 링크 목록을 사용하여 연결됩니다. 최신 행 값은 항상 현재 데이터베이스에 저장되고 tempdb에 저장된 버전이 지정된 행에 연결됩니다.

단기 실행 트랜잭션의 경우 수정 된 행의 버전이 tempdb 데이터베이스의 디스크 파일에 기록되지 않고 버퍼 풀에 캐시 될 수 있습니다. 버전이 지정된 행에 대한 필요성이 일시적인 경우 단순히 버퍼 풀에서 삭제되며 반드시 I / O 오버 헤드가 발생하는 것은 아닙니다.

추가 오버 헤드에 대해 약간의 성능 저하가있는 것처럼 보이지만 무시할 수 있습니다. 확인하기 위해 테스트해야합니다.

이 옵션을 설정하고 실제로 필요한 경우가 아니면 코드 쿼리에서 모든 NOLOCK을 제거하십시오. 데이터베이스 트랜잭션 격리 수준에 맞서기 위해 데이터베이스 컨텍스트 처리기에서 NOLOCK 또는 전역 메서드를 사용하는 것은 문제에 대한 반창고입니다. NOLOCKS는 데이터 레이어의 근본적인 문제를 가려서 자동 선택 / 업데이트 행 버전 관리가 해결책 인 것처럼 보이는 신뢰할 수없는 데이터를 선택할 수 있습니다.

ALTER Database [StackOverflow.Beta] SET READ_COMMITTED_SNAPSHOT ON


답변

NOLOCKREAD UNCOMMITTED 는 미끄러운 경사입니다. 교착 상태가 먼저 발생하는 이유를 이해하지 않는 한 절대 사용해서는 안됩니다. “모든 SQL 쿼리에 (nolock)을 추가했습니다”라고 말하는 것이 걱정됩니다. 모든 곳에 WITH NOLOCK 을 추가 해야한다는 것은 데이터 영역에 문제가 있다는 확실한 신호입니다.

업데이트 문 자체는 약간 문제가있는 것처럼 보입니다. 트랜잭션의 초기에 카운트를 결정합니까, 아니면 객체에서 가져 옵니까? AnswerCount = AnswerCount+1질문이 추가되면이 문제를 처리하는 더 좋은 방법 일 것입니다. 그러면 정확한 카운트를 얻기 위해 트랜잭션이 필요하지 않으며 잠재적으로 노출되는 동시성 문제에 대해 걱정할 필요가 없습니다.

많은 작업없이 더티 읽기를 사용하지 않고 이러한 유형의 교착 상태 문제를 해결하는 쉬운 방법 중 하나는 "Snapshot Isolation Mode"수정되지 않은 마지막 데이터를 항상 깨끗하게 읽을 수있는 SQL 2005의 새로운 기능 을 사용 하는 것입니다. 교착 상태 문을 정상적으로 처리하려면 쉽게 잡아서 재 시도 할 수 있습니다.


답변

OP 질문은이 문제가 발생한 이유를 묻는 것이 었습니다. 이 게시물은 다른 사람들이 해결할 수있는 가능한 해결책을 남기면서 그에 대한 대답을 희망합니다.

이것은 아마도 인덱스 관련 문제 일 것입니다. 예를 들어 Posts 테이블에 ParentID와 업데이트중인 필드 (AnswerCount, LastActivityDate, LastActivityUserId) 중 하나 이상을 포함하는 클러스터되지 않은 인덱스 X가 있다고 가정 해 보겠습니다.

SELECT cmd가 ParentId로 검색하기 위해 인덱스 X에서 공유 읽기 잠금을 수행 한 다음 UPDATE cmd가 쓰기 전용을 수행하는 동안 나머지 열을 가져 오기 위해 클러스터형 인덱스에 대해 공유 읽기 잠금을 수행해야하는 경우 교착 상태가 발생합니다. 클러스터형 인덱스를 잠그고이를 업데이트하려면 인덱스 X에 대한 쓰기 전용 잠금을 가져와야합니다.

이제 A가 X를 잠그고 Y를 얻으려고하는 반면 B는 Y를 잠그고 X를 얻으려고하는 상황이 있습니다.

물론 이것이 실제로 원인인지 확인하기 위해 어떤 인덱스가 사용 중인지에 대한 추가 정보로 그의 게시물을 업데이트하는 OP가 필요합니다.


답변

나는이 질문에 대해 매우 불편하고 참석자가 대답합니다. “이 마법 가루를 사용해보세요! 그 마법 가루가 아닙니다!”가 많이 있습니다.

나는 당신이 취한 자물쇠를 분석하고 어떤 정확한 유형의 자물쇠가 교착 상태인지 결정한 곳을 볼 수 없습니다.

당신이 지적한 것은 교착 상태가 아닌 일부 잠금이 발생한다는 것입니다.

SQL 2005에서는 다음을 사용하여 어떤 잠금이 해제되는지에 대한 자세한 정보를 얻을 수 있습니다.

DBCC TRACEON (1222, -1)

교착 상태가 발생하면 더 나은 진단을받을 수 있습니다.


답변

모든 작업에 대해 새 LINQ to SQL DataContext 개체를 인스턴스화하고 있습니까? 아니면 모든 호출에 대해 동일한 정적 컨텍스트를 공유하고 있습니까? 나는 원래 후자의 접근 방식을 시도했고 내가 기억하는 바에 따르면 DB에서 원치 않는 잠금이 발생했습니다. 이제 모든 원자 작업에 대한 새로운 컨텍스트를 만듭니다.


답변

NOLOCK으로 파리를 잡기 위해 집을 불 태우기 전에 Profiler로 캡처 한 데드락 그래프를 살펴볼 수 있습니다.

교착 상태에는 (적어도) 2 개의 잠금이 필요합니다. 연결 1에는 잠금 A가 있고 잠금 B를 원하며 연결 ​​2에는 그 반대의 경우도 마찬가지입니다. 이것은 해결할 수없는 상황이며 누군가 제공해야합니다.

지금까지 보여준 것은 Sql Server가 하루 종일 기꺼이 수행하는 간단한 잠금으로 해결됩니다.

나는 당신 (또는 LINQ)이 그 UPDATE 문으로 트랜잭션을 시작하고 다른 정보를 미리 선택하고 있다고 생각합니다. 그러나 각 스레드가 보유한 잠금을 찾기 위해 교착 상태 그래프를 역 추적 한 다음 프로파일 러를 통해 역 추적하여 해당 잠금이 부여 된 명령문을 찾아야합니다.

이 퍼즐을 완성하는 데 최소한 4 개의 문이있을 것으로 예상합니다 (또는 여러 잠금을 사용하는 문-아마도 Posts 테이블에 트리거가 있습니까?).


답변

사용자 프로필이 몇 초가 지난 경우에도 신경 쓰시겠습니까?

아니요-그것은 완벽하게 받아 들여집니다. 기본 트랜잭션 격리 수준을 설정하는 것이 아마도 가장 좋고 깨끗한 방법 일 것입니다.