SQL Server在维护唯一索引作为影响(或可能影响)多行的更新的一部分时,始终使用运算符的Split,Sort和Collapse组合。
通过问题中的示例,我们可以针对存在的四行中的每行,将更新编写为单独的单行更新:
-- Per row updates
UPDATE dbo.Banana SET pk = 2 WHERE pk = 1;
UPDATE dbo.Banana SET pk = 3 WHERE pk = 2;
UPDATE dbo.Banana SET pk = 4 WHERE pk = 3;
UPDATE dbo.Banana SET pk = 5 WHERE pk = 4;
问题在于第一条语句将失败,因为它pk
从1 更改为2,并且已经有一行pk
=2。SQLServer存储引擎要求唯一索引在处理的每个阶段都保持唯一,即使在单个语句内。这是通过拆分,排序和折叠解决的问题。
分裂
第一步是将每个更新语句拆分为删除,然后插入:
DELETE dbo.Banana WHERE pk = 1;
INSERT dbo.Banana (pk, c1, c2) VALUES (2, 'A', 'W');
DELETE dbo.Banana WHERE pk = 2;
INSERT dbo.Banana (pk, c1, c2) VALUES (3, 'B', 'X');
DELETE dbo.Banana WHERE pk = 3;
INSERT dbo.Banana (pk, c1, c2) VALUES (4, 'C', 'Y');
DELETE dbo.Banana WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
Split运算符将操作代码列添加到流中(此处标记为Act1007):
操作代码是1(用于更新),3(用于删除)和4(用于插入)。
分类
上面的split语句仍然会产生错误的瞬时唯一键冲突,因此下一步是按照要更新的唯一索引的键(pk
在这种情况下)然后按操作代码对语句进行排序。对于此示例,这仅意味着在插入(4)之前对同一键上的delete(3)进行排序。生成的顺序为:
-- Sort (pk, action)
DELETE dbo.Banana WHERE pk = 1;
DELETE dbo.Banana WHERE pk = 2;
INSERT dbo.Banana (pk, c1, c2) VALUES (2, 'A', 'W');
DELETE dbo.Banana WHERE pk = 3;
INSERT dbo.Banana (pk, c1, c2) VALUES (3, 'B', 'X');
DELETE dbo.Banana WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (4, 'C', 'Y');
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
坍方
前一阶段足以确保在所有情况下都避免错误的唯一性违规。作为优化,Collapse将相邻的删除操作和相同键值上的插入操作合并到更新中:
-- Collapse (pk)
DELETE dbo.Banana WHERE pk = 1;
UPDATE dbo.Banana SET c1 = 'A', c2 = 'W' WHERE pk = 2;
UPDATE dbo.Banana SET c1 = 'B', c2 = 'X' WHERE pk = 3;
UPDATE dbo.Banana SET c1 = 'C', c2 = 'Y' WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
pk
值2、3和4 的删除/插入对已合并为一个更新,在pk
= 1处保留单个删除,在= 5处保留插入pk
。
折叠运算符按关键列对行进行分组,并更新操作代码以反映折叠结果:
聚集索引更新
该运算符被标记为Update,但是它可以插入,更新和删除。每行聚簇索引更新将采取哪种操作由该行中操作代码的值确定。操作员具有一个Action属性以反映此操作模式:
行修改计数器
请注意,上面的三个更新不会修改要维护的唯一索引的键。实际上,我们已经将对索引中键列的更新转换为对非键列(c1
和c2
)的更新,以及删除和插入。删除或插入都不会导致错误的唯一键冲突。
插入或删除会影响该行中的每一列,因此与每一列关联的统计信息将使它们的修改计数器增加。对于更新,仅统计具有任何已更新列的统计信息前导列的的修改计数器才会递增(即使值保持不变)。
因此,统计行修改计数器显示2个改变pk
,和5 c1
和c2
:
-- Collapse (pk)
DELETE dbo.Banana WHERE pk = 1; -- All columns modified
UPDATE dbo.Banana SET c1 = 'A', c2 = 'W' WHERE pk = 2; -- c1 and c2 modified
UPDATE dbo.Banana SET c1 = 'B', c2 = 'X' WHERE pk = 3; -- c1 and c2 modified
UPDATE dbo.Banana SET c1 = 'C', c2 = 'Y' WHERE pk = 4; -- c1 and c2 modified
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z'); -- All columns modified
注意:仅应用于基础对象(堆或聚集索引)的更改会影响统计信息行修改计数器。非聚集索引是二级结构,反映了已对基础对象进行的更改。它们根本不影响统计信息的行修改计数器。
如果对象具有多个唯一索引,则使用单独的“拆分,排序,折叠”组合来组织每个索引的更新。SQL Server通过将拆分结果保存到“急切表假脱机”中,然后重播为每个唯一索引设置的设置(该索引将具有自己的“按索引键排序+操作代码和折叠”),从而针对非聚集索引优化了这种情况。
对统计信息更新的影响
当查询优化器需要统计信息并注意到现有统计信息已过期(或由于架构更改而无效)时,将发生自动统计信息更新(如果启用)。当记录的修改数量超过阈值时,认为统计信息已过时。
分流/排序/折叠排列结果不同行的修改被记录比预期的。反过来,这意味着与其他情况相比,统计更新可能迟早触发。
在上面的示例中,键列的行修改增加了2(净更改),而不是增加了4(受影响的每个表行增加了1)或增加了5(折叠产生的每个删除/更新/插入一次了)。
此外,原始查询在逻辑上未更改的非关键列会累积行修改,行修改的数量可能是更新的表行的两倍(每次删除一次,插入一次)。
记录的更改次数取决于新旧键列值之间的重叠程度(因此,单独的删除和插入可以折叠的程度)。在每次执行之间重置表,以下查询演示了对具有不同重叠的行修改计数器的影响:
UPDATE dbo.Banana SET pk = pk + 0; -- Full overlap
UPDATE dbo.Banana SET pk = pk + 1;
UPDATE dbo.Banana SET pk = pk + 2;
UPDATE dbo.Banana SET pk = pk + 3;
UPDATE dbo.Banana SET pk = pk + 4; -- No overlap