您需要确定需要更新的行,并使用联接或半联接来执行此操作。除非您根本不执行更新,否则它不会比这更有效率:
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 已经更新。但在这种情况下,您确实会看到两个查询,并且两个查询将真正被执行(在计划中它不会只是这样)。
一些一般性评论
由于我要提高性能,我不想在用于触发器的代码中使用任何内部联接。
这真的没有多大意义。为什么您认为联接对性能不利?听起来你已经看过太多 NoSQL 视频了。请不要因为您听说它很糟糕或者因为您曾经加入缓慢而放弃技术。创建有意义的查询,在其表现不佳时进行优化,并在无法优化时寻求帮助。在几乎所有情况下(当然也有例外),问题在于索引或统计信息,而不是您使用 JOIN 关键字的事实。这并不意味着您应该不惜一切代价避免所有查询中的所有联接。