您需要确定需要更新的行,并使用联接或半联接来执行此操作。它不会比这更有效率,除非您根本不执行更新:
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 关键字的事实。这并不意味着您应该不惜一切代价避免所有查询中的所有联接。