2014-11-08 51 views
1

我有一張用作簡單更改日誌的表;行需要按照它們寫入的順序從它讀取。該表是這樣的:類似隊列表中的死鎖,SqlDependency混合在

create table PublishedEvent (
    PublishedEventId int identity not null, 
    EntityGuid uniqueidentifier null, 
    EntityId int null, 
    EventName nvarchar(100) not null, 
    Operation nvarchar(10) not null, 
    Timestamp datetimeoffset not null default(sysdatetimeoffset()), 
    primary key (PublishedEventId) 
); 

表被寫入到從內部觸發類似於此:

create trigger Appointment_Insert on Appointment after insert as 
begin 
    insert into PublishedEvent (EntityName, EntityId, Operation) 
     select 'Appointment', i.AppointmentId, 'insert' from inserted i 
end 

有很多這樣的觸發器,所以有很多有效的作家。

在應用程序代碼中,PublishedEvent表是通過SqlDependency的實例讀取的。我使用SqlDependency是因爲我想避免輪詢數據庫,並且同時需要在新行到達時作出反應。

using (var conn = new SqlConnection(...)) 
{ 
    conn.Open(); 

    using (var tr = conn.BeginTransaction(IsolationLevel.ReadCommitted)) 
    { 
     var cmd = new SqlCommand("select PublishedEventId, EventName, EntityGuid, EntityId, Operation, Timestamp from dbo.PublishedEvent order by PublishedEventId", conn, tr); 

     var dependency = new SqlDependency(cmd); 

     dependency.OnChange += HandleDependencyChange; 

     using (var reader = cmd.ExecuteReader()) 
     { 
      while (reader.Read()) { ... } 
     } 
    } 
} 

訪問的SqlDependency被序列化,這樣只能有一次一個優秀的查詢。應用程序讀取查詢返回的PublishedEvent行,並處理它們。當然,它也會連接到SqlDependency的OnChange事件,因此循環可以重新開始。

一旦應用程序被處理完PublishedEvent行,它將刪除他們:

using (var conn = new SqlConnection(...)) 
{ 
    conn.Open(); 

    using (var tr = conn.BeginTransaction(IsolationLevel.ReadUncommitted)) 
    { 
     var cmd = new SqlCommand("delete from pe from PublishedEvent pe inner join @EventIds ei on ei.Id = pe.PublishedEventId", tr.Connection, tr); 

     cmd.Parameters.Add(new SqlParameter("@EventIds", ...)); 
     cmd.ExecuteNonQuery(); 

     tr.Commit(); 
    } 
} 

注:@EventIds是包含先前讀PublishedEvent行的PublishedEventId列的表值參數。

現在,我的問題是,我有時會在數據庫中發生死鎖。 SQL事件探查器告訴我,插入到Appointment表之間發生死鎖(可能從Foo的觸發器中插入PublishedEvent是真正的罪魁禍首)並將其刪除到PublishedEvent表。 我看過網上的相關文章,比如Using tables as Queues,但沒有一個和我有類似的約束。

有沒有避免死鎖的方法,同時有一個解決方案允許多個作者,以及使用SqlDependency的單個閱讀器?

編輯:在@usr的請求下,這裏是一個樣例死鎖圖。正如你將會看到的那樣,SQL比問題中提到的要複雜一些,儘管我不認爲我的部門會改變任何w.r.t.我的問題。

<deadlock-list> 
<deadlock victim="process5989b88"> 
    <process-list> 
    <process id="process5989b88" taskpriority="0" logused="864" waitresource="KEY: 5:72057594076397568 (7860bcaa51f1)" waittime="717" ownerId="8200442" transactionname="DeletePublishedEvents" lasttranstarted="2014-11-08T12:32:54.067" XDES="0x8004b950" lockMode="RangeS-U" schedulerid="2" kpid="9560" status="suspended" spid="56" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-11-08T12:32:54.070" lastbatchcompleted="2014-11-08T12:32:54.067" clientapp="dev-MobileMed Noyau" hostname="PLALONDEW8" hostpid="22392" loginname="PLALONDEW8\dev-MM$Core" isolationlevel="read uncommitted (1)" xactid="8200442" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> 
    <executionStack> 
    <frame procname="adhoc" line="1" stmtstart="82" sqlhandle="0x02000000fac6ff387b191d62a29fa17565e4e54bcc8aa7f5"> 
delete from pe from PublishedEvent pe inner join @EventIds ei on ei.Id = pe.PublishedEventId  </frame> 
    <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000"> 
unknown  </frame> 
    </executionStack> 
    <inputbuf> 
(@EventIds [dbo].[IntTableType] READONLY)delete from pe from PublishedEvent pe inner join @EventIds ei on ei.Id = pe.PublishedEventId </inputbuf> 
    </process> 
    <process id="process59bddc8" taskpriority="0" logused="162844" waitresource="KEY: 5:72057594076397568 (49d0d2cdfe43)" waittime="715" ownerId="8200412" transactionname="user_transaction" lasttranstarted="2014-11-08T12:32:54.060" XDES="0x805d1950" lockMode="RangeS-U" schedulerid="4" kpid="18004" status="suspended" spid="70" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-11-08T12:32:54.067" lastbatchcompleted="2014-11-08T12:32:54.067" clientapp="dev-MMImporter" hostname="PLALONDEW8" hostpid="22012" loginname="PLALONDEW8\dev-MM$Core" isolationlevel="read committed (2)" xactid="8200412" currentdb="5" lockTimeout="4294967295" clientoption1="673316896" clientoption2="128056"> 
    <executionStack> 
    <frame procname="dev-DmeDB.dbo.ScheduleSlot_InsertUpdate" line="19" stmtstart="1246" stmtend="2066" sqlhandle="0x03000500a888b73a4c40d900dca300000000000000000000"> 
insert into PublishedEvent (EventName, EntityId, Operation) 
         select &apos;ScheduleSlot&apos;, i.ScheduleSlotId, 
          case 
           when i.IsDeleted = 1 then &apos;softdelete&apos; 
           when d.ScheduleSlotId is null then &apos;insert&apos; 
           else &apos;update&apos; 
          end 
          from inserted i 
           left join deleted d on i.ScheduleSlotId = d.ScheduleSlotId;  </frame> 
    <frame procname="dev-DmeDB.medicoadmin.PLAGEHORAIRERV_AIU_REPL" line="10" stmtstart="548" stmtend="1786" sqlhandle="0x03000500bbf3ef51aabcd900dca300000000000000000000"> 
with cte (ScheduleSlotId, AppointmentTypeId, AppointmentId) as 
    (
     select ss.ScheduleSlotId, at.AppointmentTypeId, a.AppointmentId 
      from inserted i 
       inner join dbo.ScheduleSlot ss on ss.MedicoAdminId = i.ID_PLAGEHORAIRE 
       left join AppointmentType at on at.MedicoAdminId = i.ID_TYPERENDEZVOUS 
       left join Appointment a on a.MedicoAdminId = i.ID_RENDEZVOUS 
    ) 
    merge dbo.ScheduleSlot as target 
     using cte as source on (target.ScheduleSlotId = source.ScheduleSlotId) 
     when matched then 
      update set 
       AppointmentTypeId = source.AppointmentTypeId, 
       AppointmentId  = source.AppointmentId;  </frame> 
    <frame procname="adhoc" line="1" stmtstart="150" sqlhandle="0x02000000938c652d03c75d708e1fa976f91c4da900714d0c"> 
INSERT INTO [medicoadmin].PLAGEHORAIRERV (ID_PLAGEHORAIRE, ID_TYPERENDEZVOUS, ID_RENDEZVOUS) VALUES (@ID_PLAGEHORAIRE, @ID_TYPERENDEZVOUS, @ID_RENDEZVOUS);  </frame> 
    <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000"> 
unknown  </frame> 
    </executionStack> 
    <inputbuf> 
(@ID_PLAGEHORAIRE int,@ID_TYPERENDEZVOUS int,@ID_RENDEZVOUS nvarchar(4000))INSERT INTO [medicoadmin].PLAGEHORAIRERV (ID_PLAGEHORAIRE, ID_TYPERENDEZVOUS, ID_RENDEZVOUS) VALUES (@ID_PLAGEHORAIRE, @ID_TYPERENDEZVOUS, @ID_RENDEZVOUS); </inputbuf> 
    </process> 
    </process-list> 
    <resource-list> 
    <keylock hobtid="72057594076397568" dbid="5" objectname="dev-DmeDB.sys.query_notification_155199653" indexname="cidx" id="lock88780d00" mode="RangeX-X" associatedObjectId="72057594076397568"> 
    <owner-list> 
    <owner id="process59bddc8" mode="RangeX-X"/> 
    </owner-list> 
    <waiter-list> 
    <waiter id="process5989b88" mode="RangeS-U" requestType="wait"/> 
    </waiter-list> 
    </keylock> 
    <keylock hobtid="72057594076397568" dbid="5" objectname="dev-DmeDB.sys.query_notification_155199653" indexname="cidx" id="lock8f111a80" mode="RangeS-U" associatedObjectId="72057594076397568"> 
    <owner-list> 
    <owner id="process5989b88" mode="RangeS-U"/> 
    </owner-list> 
    <waiter-list> 
    <waiter id="process59bddc8" mode="RangeS-U" requestType="wait"/> 
    </waiter-list> 
    </keylock> 
    </resource-list> 
</deadlock> 
</deadlock-list> 
+0

隊列表上有哪些索引?如果每個密鑰都被插入並刪除一次,則不會發生死鎖。有些事情在這個問題上並不明顯。最多一次事務中有多少次插入和刪除?也許鎖定升級從5000鎖開始。 – usr 2014-11-08 15:23:46

+0

@usr沒有額外的索引。所有與表的交互都顯示在問題中:插入,刪除和通過SqlDependency讀取。 – 2014-11-08 16:50:13

+0

好的。將死鎖圖表作爲XML發佈。 – usr 2014-11-08 17:20:48

回答

0

爲什麼不實施一個實際的隊列?由於您已經在使用SQL Dependency,它告訴我您正在使用Service Broker(這是SQL Server中實現SQL依賴關係的基礎技術)的SQL Server版本。您的觸發器會將消息插入到SB隊列中,並且您將使用所謂的「外部激活」來告訴您的應用程序隊列中有消息需要接收。優點是隊列是爲這類事情設計的。隊列中的消息具有固有的排序(先進先出);所有你需要做的是要求下一個。 SB端無法序列化讀取訪問,但如果您只有一臺閱讀器,那應該足夠好。

我不知道您的用例的具體情況,但是如果您的應用程序正在處理事件,然後在數據庫端執行某些操作,請考慮「內部激活」。這是當你編寫一個存儲過程來處理消息時,當消息到達隊列時,數據庫引擎會自動調用該過程。在這種情況下,你可以說有多少同時讀取你想要的(在你的情況下,一個),儘管這仍然不能阻止另一個進程在內部激活過程之外從隊列中讀取消息。

+0

我不直接與Service Broker交互,因爲它看起來比通過SqlDependency複雜得多。雖然我開始認爲我沒有選擇... – 2014-11-08 16:47:19

+0

有一些移動部件可以肯定。但這並不是那麼糟糕,我向你保證。 – 2014-11-10 11:30:04