【问题标题】:SQL Server BEFORE UPDATE trigger which adds in a timestamp on a field before UPDATE executionSQL Server BEFORE UPDATE 触发器,它在 UPDATE 执行之前在字段上添加时间戳
【发布时间】:2018-08-23 20:49:59
【问题描述】:

你好 stackoverflow 的好人,

我正在为 SQL SERVER 2008 R2 上的表开发一个触发器,用于审计目的,它应该在 UPDATE 查询被发送执行之前为 UPDATE_TS 字段添加时间戳。结果是更新发生在查询时要更新的原始值加上触发器设置的 UPDATE_TS 的附加值。

我也编辑了这个问题,因为我听说与不使用它们相比,内部联接在触发器的性能方面并不是很重。我不确定这是否会增加触发器的额外开销,而不是避免触发器中的内部连接。

我正在处理的示例如下。感谢您的任何帮助和建议!

示例表名为 MY_TABLE:

CREATE TABLE [myschema].[MY_TABLE](
[MY_TABLE_ID] [bigint] IDENTITY(1,1) NOT NULL,
[FIELD_TO_UPDATE] [varchar](255) NOT NULL,
[CREATE_TS] [datetime] NULL,
[UPDATE_TS] [datetime] NULL),
PRIMARY KEY (MY_TABLE_ID))

触发创建:

CREATE TRIGGER [myschema].[my_table_update_ts_trigger] ON [mydb].[myschema].[MY_TABLE]
INSTEAD OF UPDATE
AS
BEGIN
    UPDATE INTO MY_TABLE ([FIELD_TO_UPDATE],[UPDATE_TS])
    SELECT ins.FIELD_TO_UPDATE, GETDATE() FROM INSERTED as ins
END

【问题讨论】:

    标签: sql sql-server sql-server-2008 tsql


    【解决方案1】:

    您需要确定需要更新的行,并使用联接或半联接来执行此操作。它不会比这更有效率,除非您根本不执行更新:

    CREATE TRIGGER [myschema].[my_table_update_ts_trigger] 
    ON [myschema].[MY_TABLE]
    INSTEAD OF UPDATE
    AS
    BEGIN
        UPDATE t SET 
          FIELD_TO_UPDATE = i.FIELD_TO_UPDATE,
          UPDATE_TS = CURRENT_TIMESTAMP
        FROM myschema.MY_TABLE AS t
        INNER JOIN inserted AS i
        ON t.MY_TABLE_ID = i.MY_TABLE_ID;
    END
    GO
    

    这是执行计划:

    由于您需要将inserted 中的行与您的基表相匹配,并且由于任何操作都可能更新不止一行(SQL Server 中的触发器触发每个语句,不像其他一些平台那样每行),并且因为这不是 BEFORE 更新而是 INSTEAD OF 更新(这意味着您仍然必须实际执行如果没有触发器就发生的 UPDATE ),您需要两个表的输出才能准确执行更新。这意味着您需要一个 JOIN,并且您不能使用 SEMI-JOIN(例如 EXISTS),这可能仍然违反您的古怪要求。如果您只需要更新时间戳,您可以这样做:

    UPDATE t SET UPDATE_TS = CURRENT_TIMESTAMP
      FROM myschema.MY_TABLE AS t
      WHERE EXISTS (SELECT 1 FROM inserted WHERE MY_TABLE_ID = t.MY_TABLE_ID);
    

    不幸的是,这行不通,因为FIELD_TO_UPDATE 会丢失而没有真正在正确的连接中拉入inserted 伪表。

    另一种方法是使用 CROSS APPLY,例如:

     UPDATE t SET 
       FIELD_TO_UPDATE = i.FIELD_TO_UPDATE,
       UPDATE_TS = CURRENT_TIMESTAMP
    FROM  inserted AS i
    CROSS APPLY myschema.MY_TABLE AS t
    WHERE i.MY_TABLE_ID = t.MY_TABLE_ID;
    

    它也缺少讨厌的 JOIN 关键字,但它仍在执行 JOIN。您可以看到这一点,因为执行计划是相同的:

    现在,理论上您可以在没有连接的情况下执行此操作,但这并不意味着它的性能会更好。事实上,我毫无疑问地向您保证,这会降低效率,即使它不包含像 JOIN 这样的单个四字母单词:

        DECLARE @NOW             DATETIME = CURRENT_TIMESTAMP,
                @MY_TABLE_ID     INT,
                @FIELD_TO_UPDATE VARCHAR(255);
    
        DECLARE c CURSOR LOCAL FAST_FORWARD FOR
          SELECT MY_TABLE_ID, FIELD_TO_UPDATE FROM inserted;
    
        OPEN c;
    
        FETCH NEXT FROM c INTO @FIELD_TO_UPDATE, @MY_TABLE_ID;
    
        WHILE @@FETCH_STATUS = 0
        BEGIN
          UPDATE myschema.MY_TABLE SET 
              FIELD_TO_UPDATE = @FIELD_TO_UPDATE,
                    UPDATE_TS = @NOW
            WHERE MY_TABLE_ID = @MY_TABLE_ID;
    
          FETCH NEXT FROM c INTO @FIELD_TO_UPDATE, @MY_TABLE_ID;
        END
    
        CLOSE c;
        DEALLOCATE c;
    

    也就是说,如果您认为此解决方案会比有连接的解决方案更快,那么我在佛罗里达州有一些沼泽地可以卖给您。该物业也有多座桥梁。我什至不会费心展示这个的执行计划。

    让我们也比较一下 INSTEAD OF INSERT 触发器中发生的情况。这是一个示例,可能与您的示例类似:

    CREATE TRIGGER myschema.ins_my_table
    ON myschema.MY_TABLE
    INSTEAD OF INSERT
    AS
      INSERT myschema.MY_TABLE(FIELD_TO_UPDATE, CREATE_TS)
        SELECT FIELD_TO_UPDATE, CURRENT_TIMESTAMP FROM inserted;
    GO
    

    这也会产生一个看起来像是执行了两个查询的计划:

    请务必注意,INSTEAD OF 触发器会取消原始更新,您有责任发布自己的更新(即使计划仍显示两个查询)。

    最后一个选择是使用 AFTER 触发器而不是 INSTEAD OF 触发器。这将允许您在没有 JOIN 的情况下更新时间戳,因为 FIELD_TO_UPDATE 已经更新。但在这种情况下,您确实会看到两个查询,并且确实会执行两个查询(在计划中不会只是这样)。

    一些通用的cmets

    由于我要提高性能,我不想在用于触发器的代码中使用任何内部联接。

    这真的没有多大意义;为什么您认为联接对性能不利?听起来你已经看过太多 NoSQL 视频了。请不要因为您听说它很糟糕或因为您曾经加入缓慢而放弃技术。创建有意义的查询,在性能不佳时进行优化,在无法优化时寻求帮助。在几乎所有情况下(当然也有例外),问题在于索引或统计,而不是您使用 JOIN 关键字的事实。这并不意味着您应该不惜一切代价避免所有查询中的所有联接。

    【讨论】:

    • @aruuuuu 没有不尊重,但你知道INNER JOIN 是什么吗?
    • @aruuuuu 为什么这是一个要求?如果您要求不加入 SQL,这是有史以来最糟糕的要求。
    • @aruuuuu 我真的不在乎你对加入的看法或者你为什么不希望他们在这里。这个要求没有意义,期间。这就像去赫兹说我想开车去塔尔萨,但你最好不要给我任何轮胎里有橡胶的汽车。
    • @aruuuuu 好的,当你开发了一个触发器时请告诉我:(a) 不使用 JOIN,(b) 仍然正确更新数据,并且 (c) 比我所提供的。不要忘记展示您的工作并解释您在 Profiler 中观察到的差异。 (P.S. 如果您关心性能,您当然不会将您的服务器暴露给 Profiler。)
    • @aruuuuu 看起来事实是你要求的是不好的做法,所以社区并没有真正看到鼓励这种事情的价值。
    【解决方案2】:

    如果你只是不想看到JOIN这个词,也可以,就这样写吧。

    CREATE TRIGGER [myschema].[my_table_update_ts_trigger] 
    ON [myschema].[MY_TABLE]
    INSTEAD OF UPDATE
    AS
    BEGIN
        UPDATE t
        SET FIELD_TO_UPDATE = i.FIELD_TO_UPDATE,
            UPDATE_TS       = CURRENT_TIMESTAMP
        FROM myschema.MY_TABLE AS t, 
             inserted AS i
        WHERE t.MY_TABLE_ID = i.MY_TABLE_ID;
    END
    GO
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-21
      • 2021-11-03
      • 1970-01-01
      • 1970-01-01
      • 2010-11-22
      • 2021-11-25
      • 1970-01-01
      • 2013-01-24
      相关资源
      最近更新 更多