Bế tắc SQL trên cùng một khóa được khóa riêng (với NHibernate) khi xóa / chèn


29

Tôi đã làm việc về vấn đề bế tắc này trong vài ngày nay và bất kể tôi làm gì, nó vẫn tồn tại theo cách này hay cách khác.

Đầu tiên, tiền đề chung: Chúng tôi có Lượt truy cập với VisitItems trong mối quan hệ một đến nhiều.

VisitItems thông tin liên quan:

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)

Truy cập thông tin:

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]);

Nhiều người dùng muốn cập nhật đồng thời bảng VisitItems theo cách sau:

Một yêu cầu web riêng biệt sẽ tạo một Lượt truy cập với VisitItems (thường là 1). Sau đó (yêu cầu vấn đề):

  1. Yêu cầu web xuất hiện, mở phiên NHibernate, bắt đầu giao dịch NHibernate (sử dụng Đọc lặp lại với READ_COMMITTED_SNAPSHOT trên).
  2. Đọc tất cả các mục truy cập cho một chuyến thăm nhất định của VisitId .
  3. Mã đánh giá nếu các mục vẫn còn có liên quan hoặc nếu chúng ta cần những mục mới bằng cách sử dụng các quy tắc phức tạp (vì vậy hơi lâu, ví dụ 40ms).
  4. Mã tìm thấy 1 mục cần được thêm vào, thêm nó bằng cách sử dụng NHibernate Visit.VisitItems.Add (..)
  5. Mã xác định rằng một mục cần phải bị xóa (không phải mục chúng tôi vừa thêm), xóa mục đó bằng NHibernate Visit.VisitItems.Remove (mục).
  6. Mã cam kết giao dịch

Với một công cụ tôi mô phỏng 12 yêu cầu đồng thời có khả năng xảy ra trong môi trường sản xuất trong tương lai.

[EDIT] Theo yêu cầu, đã xóa rất nhiều chi tiết điều tra mà tôi đã thêm vào đây để giữ cho nó ngắn gọn.

Sau nhiều nghiên cứu, bước tiếp theo là nghĩ cách tôi có thể khóa gợi ý về một chỉ mục khác với chỉ mục được sử dụng trong mệnh đề where (tức là khóa chính, vì nó được sử dụng để xóa), vì vậy tôi đã thay đổi câu lệnh khóa của mình thành :

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>();

Điều này làm giảm các bế tắc về tần số một chút, nhưng chúng vẫn xảy ra. Và đây là nơi tôi bắt đầu bị lạc:

Ba ổ khóa độc quyền?

<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>

Một dấu vết của số lượng truy vấn kết quả trông như thế này.
[EDIT] Whoa. Thật là một tuần. Bây giờ tôi đã cập nhật dấu vết với dấu vết chưa được xác minh của tuyên bố có liên quan mà tôi nghĩ dẫn đến bế tắc.

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

Bây giờ khóa của tôi dường như có hiệu lực vì nó hiển thị trong biểu đồ khóa chết. Nhưng cái gì? Ba khóa độc quyền và một khóa chia sẻ? Làm thế nào mà nó hoạt động trên cùng một đối tượng / khóa? Tôi nghĩ miễn là bạn có một khóa độc quyền, bạn không thể nhận được một khóa chia sẻ từ người khác? Và cách khác xung quanh. Nếu bạn có một khóa chia sẻ, không ai có thể có được khóa độc quyền, họ phải chờ.

Tôi nghĩ rằng tôi đang thiếu một số hiểu biết sâu sắc hơn ở đây về cách các khóa hoạt động khi chúng được thực hiện trên nhiều khóa trên cùng một bảng.

Dưới đây là một số điều tôi đã thử và tác động của chúng:

  • Đã thêm một gợi ý chỉ mục khác trên IX_Visit_Id vào câu lệnh khóa. Không thay đổi
  • Đã thêm một cột thứ hai vào IX_Visit_Id (Id của cột VisitItem); xa xôi, nhưng dù sao cũng đã thử. Không thay đổi
  • Đã thay đổi mức Cô lập trở lại để đọc cam kết (mặc định trong dự án của chúng tôi), các bế tắc vẫn xảy ra
  • Thay đổi mức cô lập để tuần tự hóa. Bế tắc vẫn xảy ra, nhưng tồi tệ hơn (đồ thị khác nhau). Dù sao thì tôi cũng không muốn làm điều đó.
  • Lấy một cái khóa bàn làm cho chúng biến mất (rõ ràng), nhưng ai sẽ muốn làm điều đó?
  • Sử dụng khóa ứng dụng bi quan (sử dụng sp_getapplock) hoạt động, nhưng điều đó khá giống với khóa bảng, không muốn làm điều đó.
  • Việc thêm gợi ý READPAST vào gợi ý XLOCK không tạo ra sự khác biệt
  • Tôi đã tắt PageLock trên chỉ mục và PK, không có sự khác biệt
  • Tôi đã thêm gợi ý ROWLOCK vào gợi ý XLOCK, không có sự khác biệt

Một số lưu ý phụ trên NHibernate: Cách nó được sử dụng và tôi hiểu nó hoạt động là nó lưu trữ các câu lệnh sql cho đến khi nó thực sự thấy cần thiết để thực thi chúng, trừ khi bạn gọi tuôn ra, điều mà chúng tôi đang cố gắng không làm. Vì vậy, hầu hết các câu lệnh (ví dụ: danh sách Tổng hợp được tải một cách lười biếng của VisitItems => Visit.VisitItems) chỉ được thực thi khi cần thiết. Hầu hết các báo cáo cập nhật và xóa thực tế khỏi giao dịch của tôi được thực hiện vào cuối khi giao dịch được thực hiện (như hiển nhiên từ dấu vết sql ở trên). Tôi thực sự không kiểm soát được lệnh thi hành án; NHibernate quyết định khi nào nên làm gì. Tuyên bố khóa ban đầu của tôi thực sự chỉ là một công việc xung quanh.

Ngoài ra, với tuyên bố khóa, tôi chỉ đọc các mục vào danh sách không sử dụng (Tôi không cố ghi đè danh sách VisitItems trên đối tượng Lượt truy cập vì đó không phải là cách NHibernate hoạt động theo như tôi có thể nói). Vì vậy, mặc dù tôi đã đọc danh sách đầu tiên bằng câu lệnh tùy chỉnh, NHibernate vẫn sẽ tải lại danh sách đó vào bộ sưu tập đối tượng proxy của nó Visit.VisitItems bằng cách sử dụng một cuộc gọi sql riêng biệt mà tôi có thể thấy trong dấu vết khi đến lúc tải nó một cách lười biếng ở đâu đó.

Nhưng điều đó không quan trọng, phải không? Tôi đã có khóa trên chìa khóa nói? Tải lại nó sẽ không thay đổi điều đó?

Như một lưu ý cuối cùng, có thể để làm rõ: Mỗi quy trình sẽ thêm Lượt truy cập riêng với VisitItems trước, sau đó đi vào và sửa đổi nó (sẽ kích hoạt xóa và chèn và bế tắc). Trong các thử nghiệm của tôi, không bao giờ có bất kỳ quá trình nào thay đổi chính xác Visit hoặc VisitItems.

Có ai có ý tưởng về cách tiếp cận điều này hơn nữa không? Bất cứ điều gì tôi có thể cố gắng khắc phục điều này một cách thông minh (không có khóa bảng, v.v.)? Ngoài ra, tôi muốn tìm hiểu lý do tại sao khóa tripple-x này thậm chí có thể trên cùng một đối tượng. Tôi không hiểu

Xin vui lòng cho tôi biết nếu có thêm thông tin được yêu cầu để giải câu đố.

[EDIT] Tôi đã cập nhật câu hỏi với DDL cho hai bảng có liên quan.

Ngoài ra, tôi đã được yêu cầu làm rõ về kỳ vọng: Có, một vài bế tắc ở đây và vẫn ổn, chúng tôi sẽ thử lại hoặc yêu cầu người dùng gửi lại (nói chung). Nhưng với tần suất hiện tại với 12 người dùng đồng thời, tôi hy vọng sẽ chỉ có một người cứ sau vài giờ. Hiện tại họ bật lên nhiều lần mỗi phút.

Ngoài ra, tôi có thêm một số thông tin về trancount = 2, có thể chỉ ra sự cố với các giao dịch lồng nhau, mà chúng tôi không thực sự sử dụng. Tôi cũng sẽ điều tra điều đó, và ghi lại kết quả ở đây.


2
Không sử dụng CHỌN *. Nó có thể là một yếu tố góp phần trong các vấn đề của bạn. Xem stackoverflow.com/questions/3639861/iêu
JamieSee

Ngoài ra, hãy chạy SELECT OBJECT_NAME(objectid, dbid) AS objectname, * FROM sys.dm_exec_sql_text(0x0200000024a9e43033ef90bb631938f939038627209baafb0000000000000000000000000000000000000000)cho sqlhandle trên mỗi khung execStack để xác định thêm những gì đang thực sự được thực thi.
JamieSee

Bạn đang chạy vào một vụ va chạm băm, có lẽ? dba.stackexchange.com/questions/80088/insert-only-deadlocks/ khăn
Johnboy

Chào các bạn, tôi sợ tôi không còn là một phần của dự án này nữa: - /, vì vậy tôi không thể thử các đề xuất của bạn. Tuy nhiên, tôi đã chuyển tiếp chuỗi và tất cả thông tin cho một số thành viên trong nhóm để họ có thể xem xét nó thay cho tôi.
Ben

Bạn có thể sử dụng câu trả lời tập lệnh PowerShell của tôi cho câu hỏi này để biết thêm chi tiết về sự bế tắc có thể giúp bạn. Cụ thể, nó sẽ lấy thông tin câu lệnh SQL cho các khung ngăn xếp "không xác định" của bạn. dba.stackexchange.com/questions/28996/ Mạnh
JamieSee

Câu trả lời:


2

Tôi đã đưa ra một vài nhận xét về hiệu ứng này, nhưng tôi không chắc chắn bạn sẽ nhận được kết quả mong muốn khi bạn kết hợp mức cô lập giao dịch Đọc lặp lại với Ảnh chụp nhanh đã cam kết.

TIL được báo cáo trong danh sách khóa chết của bạn là đọc lặp lại, thậm chí còn hạn chế hơn so với Đọc cam kết và đưa ra luồng bạn mô tả, có khả năng dẫn đến bế tắc.

Những gì bạn có thể đang cố gắng làm cho DB TIL của bạn vẫn được đọc lặp lại, nhưng đặt giao dịch để sử dụng TIL snapshot một cách rõ ràng với một tuyên bố mức cô lập giao dịch được đặt. Tham khảo: https://msdn.microsoft.com/en-us/l Library / ms173763.aspx Nếu vậy, tôi nghĩ bạn phải có một cái gì đó không chính xác. Tôi không quen thuộc với nHibernate, nhưng có vẻ như có một tài liệu tham khảo ở đây: http://www.anujvarma.com/fluent-nhibernate-setting-database-transaction-isolation-level/

Nếu kiến ​​trúc của ứng dụng của bạn sẽ cho phép nó, một tùy chọn sẽ là thử đọc ảnh chụp nhanh đã cam kết ở cấp db và nếu bạn vẫn gặp bế tắc, hãy bật ảnh chụp nhanh với phiên bản hàng. LƯU Ý rằng, nếu bạn làm điều này, bạn cần phải suy nghĩ lại thiết lập tempdb của mình nếu bạn bật snapshot (phiên bản hàng). Tôi có thể cung cấp cho bạn tất cả các loại tài liệu về điều này nếu bạn cần nó - cho tôi biết.


2

Tôi có một vài suy nghĩ. Trước hết, cách dễ nhất để tránh bế tắc là luôn luôn khóa theo cùng một thứ tự. Điều đó có nghĩa là các mã khác nhau sử dụng các giao dịch rõ ràng sẽ truy cập các đối tượng theo cùng một thứ tự nhưng cũng truy cập các hàng riêng lẻ theo khóa trong một giao dịch rõ ràng nên được sắp xếp trên khóa đó. Hãy thử sắp xếp Visit.VisitItemstheo PK của nó trước khi thực hiện Addhoặc Deletetrừ khi đây là một bộ sưu tập lớn trong trường hợp tôi sắp xếp SELECT.

Sắp xếp có lẽ không phải là vấn đề của bạn ở đây mặc dù. Tôi đoán 2 luồng lấy các khóa được chia sẻ trên tất cả các VisitItemIDs cho một VisitIDluồng đã cho và luồng A DELETEkhông thể hoàn thành cho đến khi luồng B giải phóng khóa chung của nó mà nó sẽ không cho đến khi DELETEhoàn thành. Khóa ứng dụng sẽ hoạt động ở đây và không tệ như khóa bảng vì chúng chỉ chặn theo phương thức và các khóa khác SELECTsẽ hoạt động tốt. Bạn cũng có thể lấy một khóa độc quyền trên Visitbàn cho VisitIDmột lần nữa, nhưng điều đó có khả năng quá mức cần thiết.

Tôi khuyên bạn nên biến xóa cứng của bạn thành xóa mềm ( UPDATE ... SET IsDeleted = 1thay vì sử dụng DELETE) và làm sạch các bản ghi này sau, hàng loạt, sử dụng một số công việc dọn dẹp không sử dụng các giao dịch rõ ràng. Điều này rõ ràng sẽ yêu cầu tái cấu trúc mã khác để bỏ qua các hàng đã xóa này nhưng là phương thức ưa thích của tôi để xử lý DELETEs được bao gồm trong một SELECTgiao dịch rõ ràng.

Bạn cũng có thể xóa SELECTkhỏi giao dịch và chuyển sang mô hình tương tranh lạc quan. Khung thực thể làm điều này miễn phí, không chắc chắn về NHibernate. EF sẽ đưa ra một ngoại lệ đồng thời lạc quan nếu DELETEtrả về 0 hàng của bạn bị ảnh hưởng.


1

Bạn đã thử di chuyển bản cập nhật Lượt truy cập trước khi có bất kỳ sửa đổi nào đối với visitItems chưa? Khóa x đó sẽ bảo vệ các hàng "con".

Thực hiện một khóa đầy đủ thu được dấu vết (và chuyển đổi thành người có thể đọc được) là rất nhiều công việc nhưng có thể hiển thị trình tự rõ ràng hơn.



-1

ĐỌC SNAPSHOT TRÊN có nghĩa là mọi giao dịch đơn lẻ chạy trong MỨC CẤP PHÂN PHỐI S READN SÀNG sẽ hoạt động như SNAPSHOT ĐÃ ĐỌC.

Điều này có nghĩa là độc giả sẽ không chặn nhà văn và nhà văn sẽ không chặn độc giả.

Bạn sử dụng mức cô lập giao dịch đọc lặp lại, đây là lý do tại sao bạn có bế tắc. Đọc cam kết (không có ảnh chụp nhanh) giữ các khóa trên các hàng / trang cho đến khi kết thúc câu lệnh , nhưng Đọc lặp lại giữ các khóa cho đến khi kết thúc giao dịch .

Nếu bạn xem biểu đồ Bế tắc của mình, bạn có thể thấy khóa "S" có được. Tôi nghĩ rằng đây là khóa của điểm thứ hai -> "Đọc tất cả các mục truy cập cho một lần truy cập nhất định của VisitId."

  1. Thay đổi mức cô lập giao dịch kết nối NHibernate của bạn thành Đọc cam kết
  2. Bạn cần phân tích truy vấn cho điểm thứ 2 của bạn và hiểu lý do tại sao nó thu được các khóa trên PK nếu bạn có một chỉ mục trên cột visitID của bạn (có thể là do thiếu các cột được bao gồm trong chỉ mục của bạn).
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.