2017-04-11 36 views
1

我創建使用SQL CLR觸發器在Microsoft SQL Server 2012的一個數據庫同步引擎,這些觸發器不調用存儲過程或函數(從而有機會獲得inserted和deleted僞表但無法訪問@@ procid)。SQL CLR觸發器 - 獲取源表

差異here,以供參考。

這種「同步引擎」使用映射表來確定哪些表和字段映射是此同步作業。爲了確定目標表和字段(從我的映射表),我需要從觸發器本身獲取源表名。我在Stack Overflow和其他網站上發現了很多答案,說這是不可能的。但是,我發現一個website,提供了一個線索:

潛在的解決方案:

using (SqlConnection lConnection = new SqlConnection(@"context connection=true")) { 
    SqlCommand cmd = new SqlCommand("SELECT object_name(resource_associated_entity_id) FROM sys.dm_tran_locks WHERE request_session_id = @@spid and resource_type = 'OBJECT'", lConnection); 
    cmd.CommandType = CommandType.Text; 
    var obj = cmd.ExecuteScalar(); 
} 

但這實際上返回正確的表名。

問:

我的問題是,如何可靠是這個潛在的解決方案? @@ spid實際上是否僅限於此單個觸發器執行?或者是否有可能其他同時觸發器會在此進程ID內重疊?它會在數據庫中多次執行相同和/或不同的觸發器嗎?

從這些網站,它似乎進程ID,其實是限制在打開的連接,不重疊:hereherehere

這會不會是一種安全的方法,讓我的源表?

爲什麼?

正如我已經注意到了類似的問題,但都沒有對我的具體情況有效的答案(除了一個)。大部分這些網站上的意見,問:「爲什麼?」,爲了搶佔的是,這是爲什麼:

此同步引擎上的單個數據庫上運行並且可將更改到目標表,改造與用戶數據自定義的源到目標類型轉換和解析,甚至可以使用CSharpCodeProvider執行也存儲在這些映射表中的方法來轉換數據。它已經建立,相當強大,並且對我們正在做的事情有很好的性能指標。我現在試圖構建它以允許1:n表更改(包括擴展表需要與「主」表相同的ID),並且試圖「代碼化」代碼。以前每個觸發器都有一個硬編碼的「目標表」定義,我使用映射表來確定源。現在我想獲取源表並使用我的映射表來確定所有的目標表。這用於中等負載環境,並將更改推送到「更改訂單簿」,單獨的服務器進程將採用該更改以完成CRUD操作。

編輯

正如在評論中提到的,上面列出的查詢是相當「前途未卜」。它會經常(例如在SQL Server重新啓動後)返回像syscolpars或sysidxstats這樣的系統對象。但是,似乎在dm_tran_locks表中,總是有一個相關的resource_type爲'RID'(行ID)和相同的object_name。我這工作可靠到目前爲止當前查詢是以下(將更新,如果這變化的情況下高負載測試不工作):如果這是總是如此

select t1.ObjectName FROM (
    SELECT object_name(resource_associated_entity_id) as ObjectName 
    FROM sys.dm_tran_locks WHERE resource_type = 'OBJECT' and request_session_id = @@spid 
) t1 inner join (
    SELECT OBJECT_NAME(partitions.OBJECT_ID) as ObjectName 
    FROM sys.dm_tran_locks 
    INNER JOIN sys.partitions ON partitions.hobt_id = dm_tran_locks.resource_associated_entity_id 
    WHERE resource_type = 'RID' 
) t2 on t1.ObjectName = t2.ObjectName 

,我必須找到了在測試期間。

+1

這是可能的。它假設引擎將精確鎖定一個對象,即觸發器執行的表。也許這總是會發生,但你不能指望任何人給你保證。我不太關心「@@ spid」 - 儘管MARS可以讓多個語句在連接上處於活動狀態,但這是一種虛假的併發。觸發器執行不會重疊,它們會按順序執行。你不應該擔心其他陳述干擾。 –

+0

確保測試當您拋出事務時會發生什麼,特別是在'SERIALIZABLE'('SELECT * FROM T WITH(HOLDLOCK)')下執行時。如果觸發器在其他鎖已被佔用的事務下執行,會發生什麼情況? –

+0

爲了清楚起見,您試圖使用一個觸發器(或者,也許是一組代碼)作爲多個表的觸發器? –

回答

0

這種潛在的解決方案有多可靠?

雖然我沒有時間來建立一個測試用例來顯示它不工作,我發現這種方法(即使考慮到在編輯部分查詢)「前途未卜」(即不保證總是是可靠的)。

的主要問題是:

  • 級聯(是否遞歸與否)觸發處決
  • 用戶(即顯式/隱式)的交易
  • 子流程(即EXECsp_executesql

這些場景允許多個對象同時被鎖定。

@@ SPID實際上是否僅限於此單個觸發器執行?或者是否有可能其他同時觸發器會在此進程ID內重疊?

和(從關於這個問題的評論):

我想我可以加入我查詢了該sys.partitions並獲得dm_trans_lock其類型爲「RID」的一個對象名稱將匹配我原來的查詢中的一個。

這就是爲什麼它不應該完全可靠:會話ID(即@@SPID)對該連接上的所有請求都是不變的)。因此,所有子過程(即EXEC調用,sp_executesql,觸發器等)將全部在相同的@@SPID/session_id上。因此,在子流程和用戶事務之間,您可以非常輕鬆地獲取多個資源上的鎖定,這些資源全部位於同一個會話ID中。

我說「資源」而不是「OBJECT」或甚至「RID」的原因是鎖可以發生在:行,頁面,鍵,表,模式,存儲過程,數據庫本身等等。多於一個東西可以被認爲是「OBJECT」,並且有可能你將擁有頁面鎖而不是行鎖。

它會站起來對數據庫中相同和/或不同觸發器的多次執行嗎?

只要這些執行發生在不同的會話中,那麼它們就不是問題。

所有這些都說明了,我可以看到簡單的測試將顯示您的當前方法是可靠的。但是,添加更詳細的測試也應該很簡單,這些測試包括首先在另一個表上執行一些DML的顯式事務,或者在一個表上具有觸發器在其中一個表上執行一些DML等。

不幸的是,沒有內置的機制可以提供@@PROCID爲T-SQL觸發器所做的功能。我想出了一個方案,應該允許獲得SQLCLR觸發器(考慮到這些不同的問題)的父表,但沒有機會測試它。它要求使用設置爲「第一個」觸發器的T-SQL觸發器來設置可由SQLCLR觸發器發現的信息。

簡單的形式可如果你是不是已經在使用它了別的使用CONTEXT_INFO構造(如果你還沒有一個「第一」觸發設置)。在這種方法中,您仍然會創建一個T-SQL觸發器,然後使用sp_settriggerorder將其設置爲「第一個」觸發器。在此觸發器中,您將SET CONTEXT_INFO添加到爲@@PROCID的父表的名稱。然後您可以在SQLCLR觸發器的上下文連接中讀取CONTEXT_INFO()。如果有多個級別的觸發器,則CONTEXT INFO的值將被覆蓋,因此讀取該值必須是每個SQLCLR觸發器中的第一件事。

+0

我知道剛剛夠SQLCLR是危險的,但有沒有什麼辦法來查詢觸發器代碼中的SqlTriggerAttribute來梳理它的屬性(在這種情況下,目標似乎相關)? –

+1

@BenThul在某種意義上是的。方法屬性存儲在程序集中,可以通過反射發現。但是,「目標」在編譯時將成爲硬編碼值。所以在同一個'SqlTrigger'方法的所有用法中,這個值是相同的。獲得不同值的唯一方法是重新編譯,因此每個目標有一個方法,即使所有'SqlTrigger'方法都調用一個常用方法,這也會增加維護成本。我將不得不對此進行測試,看看它是否可以變成更易於管理的形式,並且如果TriggerContext符合預期。 –

+0

@srutzky謝謝。不幸的是,除了爲每個使用通用方法的表使用單個觸發器定義外,我沒有辦法做到這一點。我嘗試從SqlTriggerAttribute獲取信息,但那也不適用於我。你會怎麼建議我得到那個?如果必須爲每個表分別使用不同的觸發器定義,我想在隨後的通用方法調用中對錶名進行硬編碼並不困難。我只是不想停下來看看我能真正實現它的通用性。 – Greg