SQL Deadlock pada kunci yang dikunci secara eksklusif sama (dengan NHibernate) pada delete / insert

29

Saya telah mengerjakan masalah kebuntuan ini selama beberapa hari sekarang dan tidak peduli apa yang saya lakukan, masalah ini tetap ada.

Pertama, premis umum: Kami memiliki Kunjungan dengan VisitItems dalam hubungan satu ke banyak.

Info relevan 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)

Kunjungi info:

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

Beberapa pengguna ingin memperbarui tabel VisitItems secara bersamaan dengan cara berikut:

Permintaan web terpisah akan membuat Kunjungan dengan VisitItems (biasanya 1). Kemudian (permintaan masalah):

  1. Permintaan web masuk, buka sesi NHibernate, mulai transaksi NHibernate (menggunakan Baca Berulang dengan READ_COMMITTED_SNAPSHOT aktif).
  2. Baca semua item kunjungan untuk kunjungan yang diberikan oleh VisitId .
  3. Kode menilai apakah item tersebut masih relevan atau jika kita membutuhkan yang baru menggunakan aturan yang kompleks (berjalan agak lama, misalnya 40 ms).
  4. Kode menemukan 1 item perlu ditambahkan, menambahkannya menggunakan NHibernate Visit.VisitItems.Add (..)
  5. Kode mengidentifikasi bahwa satu item perlu dihapus (bukan yang baru saja kita tambahkan), menghapusnya menggunakan NHibernate Visit.VisitItems.Remove (item).
  6. Kode melakukan transaksi

Dengan alat saya mensimulasikan 12 permintaan bersamaan yang sangat mungkin terjadi di lingkungan produksi masa depan.

[EDIT] Atas permintaan, menghapus banyak detail investigasi yang saya tambahkan di sini untuk membuatnya singkat.

Setelah banyak penelitian, langkah selanjutnya adalah memikirkan cara bagaimana saya bisa mengunci petunjuk pada indeks yang berbeda dengan yang digunakan di mana klausa (yaitu kunci utama, karena itu digunakan untuk penghapusan), jadi saya mengubah pernyataan kunci saya ke :

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

Ini mengurangi deadlock dalam frekuensi sedikit, tetapi mereka masih terjadi. Dan di sinilah saya mulai tersesat:

Tiga kunci eksklusif?

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

Jejak dari jumlah kueri yang dihasilkan terlihat seperti ini.
[EDIT] Whoa. Seminggu sekali. Saya sekarang telah memperbarui jejak dengan jejak pernyataan yang relevan yang saya pikir menyebabkan kebuntuan.

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

Sekarang kunci saya tampaknya memiliki efek karena ditampilkan di grafik jalan buntu. Tapi apa? Tiga kunci eksklusif dan satu kunci bersama? Bagaimana cara kerjanya pada objek / kunci yang sama? Saya pikir selama Anda memiliki kunci eksklusif, Anda tidak bisa mendapatkan kunci bersama dari orang lain? Dan sebaliknya. Jika Anda memiliki kunci bersama, tidak ada yang bisa mendapatkan kunci eksklusif, mereka harus menunggu.

Saya pikir saya kurang memiliki pemahaman yang lebih dalam di sini tentang bagaimana kunci bekerja ketika mereka diambil pada beberapa tombol di meja yang sama.

Berikut adalah beberapa hal yang telah saya coba dan dampaknya:

  • Menambahkan petunjuk indeks lain pada IX_Visit_Id ke pernyataan kunci. Tidak ada perubahan
  • Menambahkan kolom kedua ke IX_Visit_Id (Id dari kolom VisitItem); jauh diambil, tetapi tetap mencoba. Tidak ada perubahan
  • Tingkat Isolasi yang diubah kembali menjadi komit (default dalam proyek kami), deadlock masih terjadi
  • Tingkat Isolasi Berubah menjadi serializable. Kebuntuan masih terjadi, tetapi lebih buruk (grafik berbeda). Lagipula aku sebenarnya tidak ingin melakukan itu.
  • Mengambil kunci meja membuat mereka pergi (jelas), tetapi siapa yang mau melakukan itu?
  • Mengambil kunci aplikasi pesimistis (menggunakan sp_getapplock) berfungsi, tapi itu hampir sama dengan kunci tabel, tidak ingin melakukan itu.
  • Menambahkan petunjuk READPAST ke petunjuk XLOCK tidak ada bedanya
  • Saya telah mematikan PageLock pada indeks dan PK, tidak ada perbedaan
  • Saya telah menambahkan petunjuk ROWLOCK ke petunjuk XLOCK, tidak ada bedanya

Beberapa catatan di NHibernate: Cara ini digunakan dan saya mengerti itu berfungsi adalah bahwa cache pernyataan sql sampai benar-benar merasa perlu untuk mengeksekusi mereka, kecuali jika Anda memanggil flush, yang kami coba tidak lakukan. Jadi sebagian besar pernyataan (mis., Daftar Agregat yang malas dari VisitItems => Visit.VisitItems) dieksekusi hanya jika diperlukan. Sebagian besar pembaruan aktual dan menghapus pernyataan dari transaksi saya dieksekusi pada akhir ketika transaksi dilakukan (seperti yang terlihat dari jejak sql di atas). Saya benar-benar tidak memiliki kendali atas perintah eksekusi; NHibernate memutuskan kapan melakukan apa. Pernyataan kunci awal saya benar-benar hanya solusi.

Juga, dengan pernyataan kunci, saya hanya membaca item ke dalam daftar yang tidak digunakan (saya tidak mencoba untuk menimpa daftar VisitItems pada objek Visit karena bukan bagaimana seharusnya NHibernate bekerja sejauh yang saya tahu). Jadi, meskipun saya membaca daftar terlebih dahulu dengan pernyataan kustom, NHibernate masih akan memuat daftar lagi ke dalam koleksi objek proxy-nya. Visit.VisitItems menggunakan panggilan sql terpisah yang dapat saya lihat di dalam jejak saat saatnya memuatnya secara malas di suatu tempat.

Tapi itu tidak masalah, kan? Saya sudah memiliki kunci pada kata kunci? Memuatnya lagi tidak akan mengubah itu?

Sebagai catatan terakhir, mungkin untuk mengklarifikasi: Setiap proses menambahkan Kunjungan sendiri dengan VisitItems terlebih dahulu, kemudian masuk dan memodifikasinya (yang akan memicu hapus dan masukkan dan kebuntuan). Dalam pengujian saya, tidak pernah ada proses mengubah Visit atau VisitItems yang sama persis.

Adakah yang punya ide tentang cara mendekati ini lebih jauh? Adakah yang bisa saya coba untuk menyiasatinya dengan cara yang cerdas (tidak ada kunci meja dll)? Juga, saya ingin mempelajari mengapa kunci tripple-x ini bahkan dimungkinkan pada objek yang sama. Saya tidak mengerti.

Tolong beri tahu saya jika ada informasi lebih lanjut yang diperlukan untuk menyelesaikan puzzle.

[EDIT] Saya memperbarui pertanyaan dengan DDL untuk dua tabel yang terlibat.

Juga saya diminta untuk klarifikasi tentang harapan: Ya, beberapa kebuntuan di sini dan ada ok, kami hanya akan mencoba lagi atau meminta pengguna untuk mengirimkan kembali (secara umum). Tetapi pada frekuensi saat ini dengan 12 pengguna secara bersamaan, saya berharap hanya ada satu setiap beberapa jam paling banyak. Saat ini mereka muncul beberapa kali per menit.

Selain itu, saya mendapatkan beberapa informasi lebih lanjut tentang trancount = 2, yang mungkin mengindikasikan masalah dengan transaksi bersarang, yang sebenarnya tidak kami gunakan. Saya akan menyelidiki itu juga, dan mendokumentasikan hasilnya di sini.

Ben
sumber
2
Jangan gunakan SELECT *. Ini bisa menjadi faktor penyebab masalah Anda. Lihat stackoverflow.com/questions/3639861/…
JamieSee
Juga, jalankan SELECT OBJECT_NAME(objectid, dbid) AS objectname, * FROM sys.dm_exec_sql_text(0x0200000024a9e43033ef90bb631938f939038627209baafb0000000000000000000000000000000000000000)untuk sqlhandle pada setiap frame eksekusiStack untuk lebih menentukan apa yang sebenarnya dieksekusi.
JamieSee
Apakah Anda mengalami benturan hash, mungkin? dba.stackexchange.com/questions/80088/insert-only-deadlocks/…
Johnboy
Hai teman-teman, saya khawatir saya bukan bagian dari proyek ini lagi: - /, jadi saya tidak bisa mencoba saran Anda. Namun, saya telah meneruskan utas dan semua info kepada beberapa anggota tim sehingga mereka dapat melihatnya sebagai pengganti saya.
Ben
Anda dapat menggunakan jawaban skrip PowerShell saya untuk pertanyaan ini untuk mendapatkan lebih banyak detail kebuntuan yang dapat membantu Anda. Secara khusus, ini akan mengambil informasi pernyataan SQL untuk frame stack "tidak dikenal" Anda. dba.stackexchange.com/questions/28996/…
JamieSee

Jawaban:

2

Saya membuat beberapa komentar untuk efek ini, tetapi saya tidak yakin Anda mendapatkan hasil yang diinginkan ketika Anda menggabungkan tingkat isolasi transaksi Repeatable Read dengan Read Committed Snapshot.

TIL yang dilaporkan dalam daftar kebuntuan Anda adalah bacaan berulang, yang bahkan lebih ketat daripada Komitmen Baca, dan mengingat alur yang Anda jelaskan, kemungkinan mengarah ke kebuntuan.

Apa yang mungkin Anda coba lakukan adalah membuat DB TIL Anda tetap dapat dibaca berulang-ulang, tetapi tetapkan transaksi untuk menggunakan snapshot TIL secara eksplisit dengan pernyataan tingkat isolasi transaksi yang ditetapkan. Referensi: https://msdn.microsoft.com/en-us/library/ms173763.aspx Jika demikian, saya pikir Anda pasti memiliki sesuatu yang salah. Saya tidak terbiasa dengan nHibernate, tetapi sepertinya ada referensi di sini: http://www.anujvarma.com/fluent-nhibernate-setting-database-transaction-isolation-level/

Jika arsitektur aplikasi Anda mengizinkannya, sebuah opsi adalah mencoba membaca snapshot yang dilakukan pada level db, dan jika Anda masih mendapatkan deadlock, aktifkan snapshot dengan versi baris. Perhatikan bahwa, jika Anda melakukan ini, Anda perlu memikirkan kembali pengaturan tempdb Anda jika Anda mengaktifkan snapshot (versi baris). Saya bisa mendapatkan Anda semua jenis materi tentang ini jika Anda membutuhkannya - beri tahu saya.

Joe Hayes
sumber
2

Saya punya beberapa pemikiran. Pertama-tama, cara termudah untuk menghindari kebuntuan adalah dengan selalu mengambil kunci dalam urutan yang sama. Itu berarti kode yang berbeda menggunakan transaksi eksplisit harus mengakses objek dalam urutan yang sama tetapi juga mengakses baris secara individual dengan kunci dalam transaksi eksplisit harus diurutkan pada kunci itu. Coba urutkan Visit.VisitItemsberdasarkan PK-nya sebelum melakukan Addatau Deletekecuali ini adalah koleksi besar dalam hal ini saya akan mengurutkannya SELECT.

Penyortiran mungkin bukan masalah Anda di sini. Saya menduga 2 utas mengambil kunci bersama di semua VisitItemIDuntuk yang diberikan VisitIDdan utas A DELETEtidak dapat menyelesaikan sampai utas B melepaskan kunci bersama yang tidak akan dilakukan sampai DELETEselesai. Kunci aplikasi akan berfungsi di sini dan tidak seburuk kunci tabel karena hanya memblokir dengan metode dan yang lainnya SELECTakan berfungsi dengan baik. Anda juga bisa mengambil kunci eksklusif di atas Visitmeja untuk yang diberikanVisitID tetapi sekali lagi, itu berpotensi berlebihan.

Saya sarankan mengubah hard delete Anda menjadi soft delete ( UPDATE ... SET IsDeleted = 1alih-alih menggunakan DELETE) dan membersihkan catatan ini nanti, secara massal, menggunakan beberapa pekerjaan pembersihan yang tidak menggunakan transaksi eksplisit. Ini jelas akan memerlukan refactoring kode lain untuk mengabaikan baris yang dihapus ini tetapi metode pilihan saya untuk menangani DELETEs termasuk dalam aSELECT transaksi eksplisit.

Anda juga dapat menghapus SELECTdari transaksi dan beralih ke model konkurensi optimis. Kerangka kerja entitas melakukan ini secara gratis, tidak yakin tentang NHibernate. EF akan meningkatkan pengecualian konkurensi optimis jika DELETEpengembalian 0 baris Anda terpengaruh.

Ben Campbell
sumber
1

Sudahkah Anda mencoba memindahkan pembaruan Kunjungan sebelum modifikasi apa pun untuk mengunjungi Item? Kunci-x itu harus melindungi baris "anak".

Melakukan kunci penuh yang diperoleh jejak (dan konversi ke yang dapat dibaca manusia) adalah banyak pekerjaan tetapi mungkin menunjukkan urutan lebih jelas.

stox
sumber
-1

BACA SNAPSHOT COMMITTED ON berarti setiap transaksi yang berjalan di BACA TINGKAT ISOLASI BERKOMITMEN akan bertindak sebagai BACA SNAPSHOT BERKOMITMEN.

Ini berarti bahwa pembaca tidak akan memblokir penulis dan penulis tidak akan memblokir pembaca.

Anda menggunakan tingkat isolasi transaksi baca berulang, ini sebabnya Anda menemui jalan buntu. Komitmen Baca (tanpa snapshot) menahan kunci pada baris / halaman hingga Akhir pernyataan , tetapi Baca yang Diulang menahan kunci hingga Akhir transaksi .

Jika Anda melihat pada grafik Deadlock Anda, Anda dapat melihat kunci "S" diperoleh. Saya pikir ini adalah kunci pada poin kedua -> "Baca semua item kunjungan untuk kunjungan yang diberikan oleh VisitId."

  1. Ubah tingkat isolasi transaksi koneksi NHibernate Anda menjadi Komitmen Baca
  2. Anda perlu menganalisis kueri untuk poin ke-2 Anda dan memahami mengapa ia memperoleh kunci pada PK jika Anda memiliki indeks pada kolom visitID Anda (mungkin karena tidak ada kolom yang disertakan dalam indeks Anda).
Artashes Khachatryan
sumber