나는이 교착 상태 문제를 지금 며칠 동안 일해 왔으며 내가하는 일에 관계없이 어떤 방식 으로든 지속됩니다.
첫째, 일반적인 전제 : 우리는 일대 다 관계로 VisitItems를 방문합니다.
VisitItems 관련 정보 :
CREATE TABLE [BAR].[VisitItems] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[VisitType] INT NOT NULL,
[FeeRateType] INT NOT NULL,
[Amount] DECIMAL (18, 2) NOT NULL,
[GST] DECIMAL (18, 2) NOT NULL,
[Quantity] INT NOT NULL,
[Total] DECIMAL (18, 2) NOT NULL,
[ServiceFeeType] INT NOT NULL,
[ServiceText] NVARCHAR (200) NULL,
[InvoicingProviderId] INT NULL,
[FeeItemId] INT NOT NULL,
[VisitId] INT NULL,
[IsDefault] BIT NOT NULL DEFAULT 0,
[SourceVisitItemId] INT NULL,
[OverrideCode] INT NOT NULL DEFAULT 0,
[InvoiceToCentre] BIT NOT NULL DEFAULT 0,
[IsSurchargeItem] BIT NOT NULL DEFAULT 0,
CONSTRAINT [PK_BAR.VisitItems] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_BAR.VisitItems_BAR.FeeItems_FeeItem_Id] FOREIGN KEY ([FeeItemId]) REFERENCES [BAR].[FeeItems] ([Id]),
CONSTRAINT [FK_BAR.VisitItems_BAR.Visits_Visit_Id] FOREIGN KEY ([VisitId]) REFERENCES [BAR].[Visits] ([Id]),
CONSTRAINT [FK_BAR.VisitItems_BAR.VisitTypes] FOREIGN KEY ([VisitType]) REFERENCES [BAR].[VisitTypes]([Id]),
CONSTRAINT [FK_BAR.VisitItems_BAR.FeeRateTypes] FOREIGN KEY ([FeeRateType]) REFERENCES [BAR].[FeeRateTypes]([Id]),
CONSTRAINT [FK_BAR.VisitItems_CMN.Users_Id] FOREIGN KEY (InvoicingProviderId) REFERENCES [CMN].[Users] ([Id]),
CONSTRAINT [FK_BAR.VisitItems_BAR.VisitItems_SourceVisitItem_Id] FOREIGN KEY ([SourceVisitItemId]) REFERENCES [BAR].[VisitItems]([Id]),
CONSTRAINT [CK_SourceVisitItemId_Not_Equal_Id] CHECK ([SourceVisitItemId] <> [Id]),
CONSTRAINT [FK_BAR.VisitItems_BAR.OverrideCodes] FOREIGN KEY ([OverrideCode]) REFERENCES [BAR].[OverrideCodes]([Id]),
CONSTRAINT [FK_BAR.VisitItems_BAR.ServiceFeeTypes] FOREIGN KEY ([ServiceFeeType]) REFERENCES [BAR].[ServiceFeeTypes]([Id])
)
CREATE NONCLUSTERED INDEX [IX_FeeItem_Id]
ON [BAR].[VisitItems]([FeeItemId] ASC)
CREATE NONCLUSTERED INDEX [IX_Visit_Id]
ON [BAR].[VisitItems]([VisitId] ASC)
방문 정보 :
CREATE TABLE [BAR].[Visits] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[VisitType] INT NOT NULL,
[DateOfService] DATETIMEOFFSET NOT NULL,
[InvoiceAnnotation] NVARCHAR(255) NULL ,
[PatientId] INT NOT NULL,
[UserId] INT NULL,
[WorkAreaId] INT NOT NULL,
[DefaultItemOverride] BIT NOT NULL DEFAULT 0,
[DidNotWaitAdjustmentId] INT NULL,
[AppointmentId] INT NULL,
CONSTRAINT [PK_BAR.Visits] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_BAR.Visits_CMN.Patients] FOREIGN KEY ([PatientId]) REFERENCES [CMN].[Patients] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_BAR.Visits_CMN.Users] FOREIGN KEY ([UserId]) REFERENCES [CMN].[Users] ([Id]),
CONSTRAINT [FK_BAR.Visits_CMN.WorkAreas_WorkAreaId] FOREIGN KEY ([WorkAreaId]) REFERENCES [CMN].[WorkAreas] ([Id]),
CONSTRAINT [FK_BAR.Visits_BAR.VisitTypes] FOREIGN KEY ([VisitType]) REFERENCES [BAR].[VisitTypes]([Id]),
CONSTRAINT [FK_BAR.Visits_BAR.Adjustments] FOREIGN KEY ([DidNotWaitAdjustmentId]) REFERENCES [BAR].[Adjustments]([Id]),
);
CREATE NONCLUSTERED INDEX [IX_Visits_PatientId]
ON [BAR].[Visits]([PatientId] ASC);
CREATE NONCLUSTERED INDEX [IX_Visits_UserId]
ON [BAR].[Visits]([UserId] ASC);
CREATE NONCLUSTERED INDEX [IX_Visits_WorkAreaId]
ON [BAR].[Visits]([WorkAreaId]);
여러 사용자가 다음과 같은 방법으로 VisitItems 테이블을 동시에 업데이트하려고합니다.
별도의 웹 요청은 VisitItems (일반적으로 1)로 방문을 만듭니다. 그런 다음 (문제 요청) :
- 웹 요청이 들어 와서 NHibernate 세션을 열고 NHibernate 트랜잭션을 시작합니다 (READ_COMMITTED_SNAPSHOT이 설정된 반복 읽기 사용).
- VisitId 가 특정 방문에 대한 모든 방문 항목을 읽습니다 .
- 코드는 항목이 여전히 관련성이 있는지 또는 복잡한 규칙을 사용하여 새로운 항목이 필요한지 평가합니다 (예 : 40ms와 같이 약간 길어짐).
- 코드에서 1 개의 항목을 추가해야하고 NHibernate Visit.VisitItems.Add (..)를 사용하여 추가해야합니다.
- 코드는 방금 추가 한 항목이 아닌 하나의 항목을 삭제해야한다는 것을 식별하고 NHibernate Visit.VisitItems.Remove (item)를 사용하여 제거합니다.
- 코드가 트랜잭션을 커밋
도구를 사용하여 향후 프로덕션 환경에서 발생할 가능성이 높은 12 개의 동시 요청을 시뮬레이션합니다.
[편집] 요청에 따라 여기에 추가 한 조사 세부 정보를 많이 삭제했습니다.
많은 연구를 한 후 다음 단계는 where 절에 사용 된 것과 다른 색인에 힌트를 잠글 수있는 방법을 생각하는 것이 었습니다 (즉, 기본 키는 삭제에 사용되었으므로). :
var items = (List<VisitItem>)_session.CreateSQLQuery(@"SELECT * FROM BAR.VisitItems WITH (XLOCK, INDEX([PK_BAR.VisitItems]))
WHERE VisitId = :visitId")
.AddEntity(typeof(VisitItem))
.SetParameter("visitId", qi.Visit.Id)
.List<VisitItem>();
이로 인해 교착 상태가 약간 줄어들었지만 여전히 발생하고 있습니다. 그리고 여기 내가 길을 잃기 시작하는 곳이 있습니다.
<deadlock-list>
<deadlock victim="process3f71e64e8">
<process-list>
<process id="process3f71e64e8" taskpriority="0" logused="0" waitresource="KEY: 5:72057594071744512 (a5e1814e40ba)" waittime="3812" ownerId="8004520" transactionname="user_transaction" lasttranstarted="2015-12-14T10:24:58.010" XDES="0x3f7cb43b0" lockMode="X" schedulerid="1" kpid="15788" status="suspended" spid="63" sbid="0" ecid="0" priority="0" trancount="1" lastbatchstarted="2015-12-14T10:24:58.013" lastbatchcompleted="2015-12-14T10:24:58.013" lastattention="1900-01-01T00:00:00.013" clientapp=".Net SqlClient Data Provider" hostname="ABC" hostpid="10016" loginname="bsapp" isolationlevel="repeatable read (3)" xactid="8004520" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="18" stmtend="254" sqlhandle="0x0200000024a9e43033ef90bb631938f939038627209baafb0000000000000000000000000000000000000000">
unknown
</frame>
<frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown
</frame>
</executionStack>
<inputbuf>
(@p0 int)SELECT * FROM BAR.VisitItems WITH (XLOCK, INDEX([PK_BAR.VisitItems]))
WHERE VisitId = @p0
</inputbuf>
</process>
<process id="process4105af468" taskpriority="0" logused="1824" waitresource="KEY: 5:72057594071744512 (8194443284a0)" waittime="3792" ownerId="8004519" transactionname="user_transaction" lasttranstarted="2015-12-14T10:24:58.010" XDES="0x3f02ea3b0" lockMode="S" schedulerid="8" kpid="15116" status="suspended" spid="65" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-12-14T10:24:58.033" lastbatchcompleted="2015-12-14T10:24:58.033" lastattention="1900-01-01T00:00:00.033" clientapp=".Net SqlClient Data Provider" hostname="ABC" hostpid="10016" loginname="bsapp" isolationlevel="repeatable read (3)" xactid="8004519" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="18" stmtend="98" sqlhandle="0x0200000075abb0074bade5aa57b8357410941428df4d54130000000000000000000000000000000000000000">
unknown
</frame>
<frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown
</frame>
</executionStack>
<inputbuf>
(@p0 int)DELETE FROM BAR.VisitItems WHERE Id = @p0
</inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594071744512" dbid="5" objectname="BAR.VisitItems" indexname="PK_BAR.VisitItems" id="lock449e27500" mode="X" associatedObjectId="72057594071744512">
<owner-list>
<owner id="process4105af468" mode="X"/>
</owner-list>
<waiter-list>
<waiter id="process3f71e64e8" mode="X" requestType="wait"/>
</waiter-list>
</keylock>
<keylock hobtid="72057594071744512" dbid="5" objectname="BAR.VisitItems" indexname="PK_BAR.VisitItems" id="lock46a525080" mode="X" associatedObjectId="72057594071744512">
<owner-list>
<owner id="process3f71e64e8" mode="X"/>
</owner-list>
<waiter-list>
<waiter id="process4105af468" mode="S" requestType="wait"/>
</waiter-list>
</keylock>
</resource-list>
</deadlock>
</deadlock-list>
결과 쿼리 수의 추적은 다음과 같습니다.
[EDIT] 워. 일주일 이요 교착 상태로 이어질 것으로 생각되는 관련 문장의 수정되지 않은 흔적으로 추적을 업데이트했습니다.
exec sp_executesql N'SELECT * FROM BAR.VisitItems WITH (XLOCK, INDEX([PK_BAR.VisitItems]))
WHERE VisitId = @p0',N'@p0 int',@p0=3826
go
exec sp_executesql N'SELECT visititems0_.VisitId as VisitId1_, visititems0_.Id as Id1_, visititems0_.Id as Id37_0_, visititems0_.VisitType as VisitType37_0_, visititems0_.FeeItemId as FeeItemId37_0_, visititems0_.FeeRateType as FeeRateT4_37_0_, visititems0_.Amount as Amount37_0_, visititems0_.GST as GST37_0_, visititems0_.Quantity as Quantity37_0_, visititems0_.Total as Total37_0_, visititems0_.ServiceFeeType as ServiceF9_37_0_, visititems0_.ServiceText as Service10_37_0_, visititems0_.InvoiceToCentre as Invoice11_37_0_, visititems0_.IsDefault as IsDefault37_0_, visititems0_.OverrideCode as Overrid13_37_0_, visititems0_.IsSurchargeItem as IsSurch14_37_0_, visititems0_.VisitId as VisitId37_0_, visititems0_.InvoicingProviderId as Invoici16_37_0_, visititems0_.SourceVisitItemId as SourceV17_37_0_ FROM BAR.VisitItems visititems0_ WHERE visititems0_.VisitId=@p0',N'@p0 int',@p0=3826
go
exec sp_executesql N'INSERT INTO BAR.VisitItems (VisitType, FeeItemId, FeeRateType, Amount, GST, Quantity, Total, ServiceFeeType, ServiceText, InvoiceToCentre, IsDefault, OverrideCode, IsSurchargeItem, VisitId, InvoicingProviderId, SourceVisitItemId) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15); select SCOPE_IDENTITY()',N'@p0 int,@p1 int,@p2 int,@p3 decimal(28,5),@p4 decimal(28,5),@p5 int,@p6 decimal(28,5),@p7 int,@p8 nvarchar(4000),@p9 bit,@p10 bit,@p11 int,@p12 bit,@p13 int,@p14 int,@p15 int',@p0=1,@p1=452,@p2=1,@p3=0,@p4=0,@p5=1,@p6=0,@p7=1,@p8=NULL,@p9=0,@p10=1,@p11=0,@p12=0,@p13=3826,@p14=3535,@p15=NULL
go
exec sp_executesql N'UPDATE BAR.Visits SET VisitType = @p0, DateOfService = @p1, InvoiceAnnotation = @p2, DefaultItemOverride = @p3, AppointmentId = @p4, ReferralRequired = @p5, ReferralCarePlan = @p6, UserId = @p7, PatientId = @p8, WorkAreaId = @p9, DidNotWaitAdjustmentId = @p10, ReferralId = @p11 WHERE Id = @p12',N'@p0 int,@p1 datetimeoffset(7),@p2 nvarchar(4000),@p3 bit,@p4 int,@p5 bit,@p6 nvarchar(4000),@p7 int,@p8 int,@p9 int,@p10 int,@p11 int,@p12 int',@p0=1,@p1='2016-01-22 12:37:06.8915296 +08:00',@p2=NULL,@p3=0,@p4=NULL,@p5=0,@p6=NULL,@p7=3535,@p8=4246,@p9=2741,@p10=NULL,@p11=NULL,@p12=3826
go
exec sp_executesql N'DELETE FROM BAR.VisitItems WHERE Id = @p0',N'@p0 int',@p0=7919
go
이제 내 잠금은 교착 상태 그래프에 표시되므로 효과가있는 것 같습니다. 근데 뭐? 3 개의 독점 잠금 및 1 개의 공유 잠금? 동일한 객체 / 키에서 어떻게 작동합니까? 독점 잠금이있는 한 다른 사람으로부터 공유 잠금을 얻을 수 없다고 생각 했습니까? 그리고 다른 방법. 공유 잠금이 있으면 아무도 독점 잠금을 얻을 수 없으며 대기해야합니다.
동일한 테이블의 여러 키에서 잠금을 사용할 때 잠금이 작동하는 방식에 대한 이해가 부족하다고 생각합니다.
내가 시도한 것들과 그 영향은 다음과 같습니다.
- IX_Visit_Id에 대한 또 다른 색인 힌트를 잠금 문에 추가했습니다. 변경 없음
- IX_Visit_Id (방문 항목 열의 ID)에 두 번째 열을 추가했습니다. 멀리 가져 왔지만 어쨌든 시도했습니다. 변경 없음
- 격리 수준을 커밋 된 읽기 (프로젝트의 기본값)로 다시 변경했는데 교착 상태가 여전히 발생 함
- 격리 수준을 직렬화 가능으로 변경했습니다. 교착 상태는 여전히 발생하지만 더 나쁩니다 (다른 그래프). 어쨌든 나는 그렇게하고 싶지 않습니다.
- 테이블 잠금 장치를 사용하면 테이블이 사라지지만 누가 그렇게하겠습니까?
- sp_getapplock을 사용하여 비관적 응용 프로그램 잠금을 사용하면 작동하지만 테이블 잠금과 거의 동일합니다.
- XLOCK 힌트에 READPAST 힌트를 추가해도 아무런 차이가 없습니다.
- 색인 및 PK에서 PageLock을 해제했습니다. 차이가 없습니다.
- XLOCK 힌트에 ROWLOCK 힌트를 추가했지만 아무런 차이가 없었습니다.
NHibernate에 대한 몇 가지 참고 사항 : 사용 된 방법과 작동 방식은 플러시를 호출하지 않는 한 실제로 실행해야 할 때까지 sql 문을 캐시한다는 것입니다. 따라서 대부분의 명령문 (예 : 느리게로드 된 VisitItems => Visit.VisitItems의 집계 목록)은 필요할 때만 실행됩니다. 내 트랜잭션의 실제 업데이트 및 삭제 문 대부분은 트랜잭션이 커밋되면 끝납니다 (위의 SQL 추적에서 알 수 있음). 나는 실제로 실행 순서를 통제 할 수 없다. NHibernate는 언제 무엇을해야할지 결정합니다. 내 초기 잠금 문은 실제로 해결 방법입니다.
또한 lock 문을 사용하여 항목을 사용하지 않는 목록으로 읽습니다 (방문객 개체의 VisitItems 목록을 재정의하려고하지 않습니다. 왜냐하면 NHibernate가 내가 알 수있는 한 작동하지 않기 때문입니다). 따라서 사용자 지정 문으로 목록을 먼저 읽었더라도 NHibernate는 여전히 어딘가에로드 할 때 추적에서 볼 수있는 별도의 SQL 호출을 사용하여 프록시 객체 컬렉션 Visit.VisitItems에 다시 목록을로드합니다.
그러나 그것은 중요하지 않습니다. 이미 열쇠에 자물쇠가 있습니까? 다시로드해도 변경되지 않습니까?
마지막으로, 명확히 할 수 있습니다 : 각 프로세스는 먼저 VisitItems를 사용하여 자체 방문을 추가 한 다음 수정하고 삭제합니다 (삭제 및 삽입 및 교착 상태를 유발합니다). 내 테스트에서 정확히 동일한 방문 또는 방문 항목을 변경하는 프로세스는 없습니다.
아무도 이것에 더 접근하는 방법에 대한 아이디어가 있습니까? 현명한 방법으로 (테이블 잠금 없음)이 문제를 해결할 수 있습니까? 또한이 tripple-x 잠금이 동일한 객체에서 가능한 이유를 배우고 싶습니다. 이해가 안 돼요
퍼즐을 풀기 위해 더 많은 정보가 필요하면 알려주십시오.
[편집] 관련된 두 테이블에 대해 DDL로 질문을 업데이트했습니다.
또한 나는 기대에 대한 설명을 요구 받았다. 네, 여기에 몇 가지 교착 상태가 있고 괜찮습니다. 다시 시도하거나 사용자가 다시 제출하도록하십시오 (일반적으로 말하면). 그러나 12 명의 동시 사용자가있는 현재 빈도에서는 최대 몇 시간마다 한 명만있을 것으로 예상합니다. 현재는 분당 여러 번 나타납니다.
또한 trancount = 2에 대한 정보가 더 있는데, 실제로 사용하지 않는 중첩 트랜잭션에 문제가 있음을 나타냅니다. 나는 그것을 조사하고 여기에 결과를 문서화 할 것이다.
SELECT OBJECT_NAME(objectid, dbid) AS objectname, * FROM sys.dm_exec_sql_text(0x0200000024a9e43033ef90bb631938f939038627209baafb0000000000000000000000000000000000000000)
각 executionStack 프레임에서 sqlhandle에 대해 실행하여 실제로 실행중인 항목을 추가로 판별하십시오.