Rom*_*kar 7 trigger performance sql-server partitioning sql-server-2016
我不确定我发现的是否是一个错误,但它看起来确实是这样。我找不到太多有关它的信息,所以我决定把它放在这里。
因此,简而言之,在访问内部表(inserted
和deleted
分区表上定义的触发器中的
为了测试这个问题,我创建了一些简单的表,它们完全相同,但一个是分区的,另一个不是:
create table [dbo].[Test1](
[part_id] [int] not null,
[id] [int] not null,
[cost] [float] null,
constraint [pk__Test1] primary key clustered ([part_id] asc, [id] asc) on ps_part(part_id)
);
create table [dbo].[Test2](
[part_id] [int] not null,
[id] [int] not null,
[cost] [float] null,
constraint [pk__Test1] primary key clustered ([part_id] asc, [id] asc)
);
Run Code Online (Sandbox Code Playgroud)
然后我用一些数据填充了表格。我现在没有数据生成脚本,我只是使用了一些本地数据,但是这些表中有大约 473 个不同的分区和大约 383M 行。
然后我刚刚测试了这些表的更新速度,使用非常简单的查询,例如
update dbo.Test1 set cost = cost + 0.1 where part_id = ??;
update dbo.Test1 set cost = cost - 0.1 where part_id = ??;
update dbo.Test2 set cost = cost + 0.1 where part_id = ??;
update dbo.Test2 set cost = cost - 0.1 where part_id = ??;
Run Code Online (Sandbox Code Playgroud)
结果是合乎逻辑的 - 分区表的平均更新时间约为2 秒,非分区表的平均更新时间约为4 秒。
然后我在两个表上创建了简单的触发器
alter trigger [dbo].[Test1__changed] on [dbo].[Test1]
after insert,update,delete
as
begin
set nocount on;
select a.part_id
into #temp11111111
from (
select r.part_id from inserted as r
union
select r.part_id from deleted as r
) as a;
end
Run Code Online (Sandbox Code Playgroud)
之后我尝试了相同的测试查询,结果非常奇怪 - 在分区表上,查询平均需要3 分钟才能完成,而在非分区表上,时间与没有触发器的时间相似 -大约4秒。
你知道为什么会发生这种情况吗?有什么办法可以解决这个问题吗?
Alex发现的KB 2606883中记录了 SQL Server 2012 的此问题。
我在 SQL Server 2019 中重现了该问题,并验证设置跟踪标志 2470 可以解决该问题。
我不清楚该修复的具体性质是什么,以及为什么这不是后续版本中产品的默认行为。
对于我的示例数据(3000 万行均匀分布在 700 个分区上,其中有一个空分区),这使插入扫描和删除扫描所需的时间从每个 > 4 秒减少到每个 0.021 秒。
TF 打开和关闭的执行计划(包括运行时统计信息)位于此处。在这两个计划中,删除/插入的扫描仍然表示它们访问所有 701 分区。
添加马丁关于修复性质的答案:
如果没有修复,为支持触发器而生成的行版本将通过与事务关联的单个存储引擎行集句柄进行访问。
插入和删除的伪表的扫描被标记为已分区。每个分区都是通过单个行集句柄扫描版本列表来处理的。在示例中,版本列表被扫描了 700 次。
当导致触发器触发的语句仅影响几个分区(或示例中的单个分区)时,这会导致大量不必要的版本扫描。在该示例中,除了一个版本扫描之外的所有版本扫描都将找不到匹配的记录。
触发器本身是一个单独的执行。它对受触发语句影响的分区集一无所知。
TF 2470 下的修复会为每个分区创建一个存储引擎行集句柄,但前提是底层分区对象具有 100 个或更多分区。句柄的数量按比例放大到最接近的 2 的幂(例如,为 100 个分区提供 128 个句柄)。
插入和删除的触发器内插入和删除的扫描仍然每个分区的行集句柄通过仅访问与特定分区关联的版本而不是每次访问整个集,从而提高了效率。
sqlmin!RowsetVersionScan::Init
此更改发生在存储引擎内的非常低的级别 ( ),并且通过任何 DMV 都是不可见的。考虑到触发器可以与其他引擎功能交互的所有方式,这是一个相当危险的更改,因此它受到跟踪标志的保护,并且默认情况下不打开。受此问题影响的用户可以启用该标志,并在必要时快速恢复。
归档时间: |
|
查看次数: |
1052 次 |
最近记录: |