2009-10-31 17 views
2

我有以下觸發器,當它運行這會導致一個錯誤:sp_addlinkserver使用觸發

CREATE TRIGGER ... 
ON ... 
FOR INSERT, UPDATE 
AS 

IF UPDATE(STATUS) 
BEGIN 

    DECLARE @newPrice VARCHAR(50) 
    DECLARE @FILENAME VARCHAR(50) 
    DECLARE @server VARCHAR(50) 
    DECLARE @provider VARCHAR(50) 
    DECLARE @datasrc VARCHAR(50) 
    DECLARE @location VARCHAR(50) 
    DECLARE @provstr VARCHAR(50) 
    DECLARE @catalog VARCHAR(50) 
    DECLARE @DBNAME VARCHAR(50) 

    SET @server=xx 
    SET @provider=xx 
    SET @datasrc=xx 
    SET @provstr='DRIVER={SQL Server};SERVER=xxxxxxxx;UID=xx;PWD=xx;' 
    SET @DBNAME='[xx]' 

    SET @newPrice = (SELECT STATUS FROM Inserted) 
    SET @FILENAME = (SELECT INPUT_XML_FILE_NAME FROM Inserted) 

    IF @newPrice = 'FAIL'  
    BEGIN 
     EXEC master.dbo.sp_addlinkedserver 
      @server, '', @provider, @datasrc, @provstr 

     EXEC master.dbo.sp_addlinkedsrvlogin @server, 'true' 

     INSERT INTO [@server].[@DBNAME].[dbo].[maildetails] 
     (
      'to', 'cc', 'from', 'subject', 'body', 'status', 
      'Attachment', 'APPLICATION', 'ID', 'Timestamp', 'AttachmentName' 
     ) 
     VALUES 
     (
      'P23741', '', '', 'XMLFAILED', @FILENAME, '4', 
      '', '8', '', GETDATE(), '' 
     ) 

     EXEC sp_dropserver @server 
    END 

END 

錯誤是:

消息15002,級別16,狀態1,過程sp_MSaddserver_internal,行28 過程'sys.sp_addlinkedserver'不能在事務中執行。 消息15002,級別16,狀態1,過程sp_addlinkedsrvlogin,第17行 過程'sys.sp_addlinkedsrvlogin'不能在事務中執行。 消息15002,級別16,狀態1,過程sp_dropserver,行12 過程'sys.sp_dropserver'不能在事務中執行。

如何防止發生此錯誤?

回答

1

由於錯誤明確指出,您不能在事務中添加鏈接的服務器。觸發器作爲隱式事務運行,所以如果觸發器執行任何驗證並且驗證失敗,您可以使用ROLLBACK

我真的無法想象爲什麼你要這麼做。除了幾個非常罕見的例外情況,我不打算在這裏提到,鏈接服務器並不是理想的臨時固定設備。只需添加鏈接服務器一次,永久性的,那麼你就不會有這個問題。您還可以將其管理和安全視爲管理功能,而不是將其硬編碼到某個腳本的腳本中。

+0

永久鏈接服務器是服務器級別的對象。對於我們這些與其他開發團隊共享服務器的人來說,這是與那些可以避免的開發團隊的又一個協調點。如果您使用自動構建工具(如NAnt)協調跨環境的DDL更改部署,則需要儘可能在數據庫級別或更低級別的內容。 – alyssackwan 2010-03-01 20:03:32

+0

@entaroadun:另一方面是'OPENROWSET'既需要源服務器的特殊權限,又需要保存憑據到目的地。這對於開發來說可能不是問題,但許多團隊可能不希望在生產中使用它。 – Aaronaught 2010-03-01 20:10:19

1

如果您想動態地到達遠程服務器,請使用OPENROWSET。我一直這樣做。

1

您的觸發器在很多方面都不好。首先,您不能在觸發器中添加鏈接的服務器。您應該在管理上添加一次,而不必再擔心它。

即使連接的服務器不見了,接下來也非常重要的是,如果插入並更新了一行,並且在發生多行插入/更新時無法正常工作,那麼觸發器纔會起作用。這是觸發器設計的第一個,也是最基本的規則,從不假定只有一行將被處理。任何時候我看到一個使用的值子句或從插入或刪除的值設置爲變量我知道觸發器是壞的,需要重寫。現在這似乎發出一封電子郵件,如果很多記錄被更新或只有一個記錄被更新,你真的想發送1907898電子郵件嗎?如果你只需要一個,你需要一種方法來識別所有受影響的ID。如果有人需要更新大量價格,並通過基於集合的更新統計信息而不是手動通過用戶界面以10,000的價格進行更新,您真的希望發生什麼?並且不要說有史以來的一個記錄會被更新或插入。遲早有人需要做批量插入或更新,而你的觸發器會默默地導致錯誤的事情發生。你甚至不會知道它失敗了,因爲它不會錯誤,它根本不會做你需要它做的事情。這就是噩夢,無法修復,數據完整性問題。

另一件事,這是寫入數據庫的方式不會改變,所以你不再需要一個變量與鏈接服務器的東西刪除。否則,如果你必須更改爲動態SQL才能使其正常工作,並且這在觸發器(或其他任何地方通常)中是一個糟糕的主意,並且由於它不會發生變化,因此根本沒有理由使用它。

基於集合的解決方案(假設你想爲每一個插入或更新,並假設您建立永久的鏈接服務器項目的一個記錄):

INSERT INTO myserver].mydatabase.[dbo].[maildetails] 
     ( 
      'to', 'cc', 'from', 'subject', 'body', 'status', 
      'Attachment', 'APPLICATION', 'ID', 'Timestamp', 'AttachmentName' 
SELECT 'P23741', '', '', 'XMLFAILED', INPUT_XML_FILE_NAME , '4', 
      '', '8', '', GETDATE(), '' 
FROM inserted 
WHERE status = 'Fail' 

我最後警告你的是,即使是這樣,如果鏈接的服務器出於任何原因關閉,則會失敗。這意味着它停機時,表中不能添加或更改任何記錄。在將其置於觸發器之前,請仔細考慮這一點。