Here is as small repro: create table dbo.t (id int primary key, v int); insert into dbo.t values (1, 1), (2, 2); create table dbo.s (id int primary key, v int); insert into dbo.s values (1, 10); go create trigger dbo.tr_t__iou on dbo.t instead of update as begin set nocount on; exec sp_lock @@spid; end; go update dbo.t set v = 10 where id = 1; update t set v = 10 from dbo.s s join dbo.t t on t.id = s.id; update t set v = 10 from (values (1, 10)) s(id, v) join dbo.t t on t.id = s.id; go drop table dbo.t, dbo.s; go `sp_lock` within the `trigger` reports U-`key lock` on the affected row in the first and the last cases, but in the second case there is no `lock` at all, how can it be explained?
When the update statement qualifies for a trivial plan, the optimizer rule that expands the instead-of trigger part of the statement (`ExpandInsteadOfTriggerUpd`) includes the part of the plan that reads from the base table. This rewrite includes adding an `UPDLOCK` hint to the base read. As usual, the `UPDLOCK` hint means that update locks are taken and held to the end of the transaction. When the statement does not qualify for a trivial plan, the `ExpandInsteadOfTriggerUpd` rule only [rewrites the write-cursor portion][1] of the plan, leaving the base table reads untouched - no `UPDLOCK` hint is added. My guess is that this trivial plan behaviour exists to avoid a deadlock scenario. [1]: https://sqlperformance.com/2015/09/sql-plan/instead-of-triggers