2

我有以下触发器,它在运行时会导致错误:

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' 不能在事务中执行。

如何防止发生此错误?

4

3 回答 3

1

如果要动态访问远程服务器,请使用 OPENROWSET。我一直都这样做。

于 2010-03-01T20:00:59.407 回答
1

正如错误明确指出的那样,您不能在事务中添加链接服务器。触发器作为隐式事务运行,因此ROLLBACK如果触发器执行任何验证并且验证失败,您可以这样做。

我真的无法想象你为什么要这样做。除了一些非常罕见的例外,我不想在这里提及,链接服务器作为临时固定装置并不理想。只需添加一次链接服务器,永久,然后您将不会遇到此问题。您还可以将其管理和安全性视为管理功能,而不是将其硬编码在某个脚本中。

于 2010-02-23T22:29:00.203 回答
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'

我对您的最后警告是,如果链接服务器因任何原因出现故障,即使这样也会失败。这意味着当它关闭时,不能在表中添加或更改任何记录。在将其放入触发器之前,请仔细考虑这一点。

于 2010-03-01T20:23:37.513 回答