在大型表上使用触发器的 DELETE 语句上的 SQL 估计值相差甚远

Dan*_*vey 16 sql-server delete execution-plan sql-server-2016 query-performance

我正在使用 Microsoft SQL Server 2016 (SP2-CU11) (KB4527378) - 13.0.5598.27 (X64) Nov 27 2019 18:09:22 版权所有 (c) Windows Server 2012 R2 上的 Microsoft Corporation 标准版(64 位)标准 6.3(内部版本 9600:)

该服务器位于 SSD 驱动器上,最大内存为 128 GB。Parallelism 的 CostTheshold 是 70,MaxDegree of Parallelism 是 3。

我有一个“行程”表,它由 23 个外键引用,带有 ON DELETE CASCADE 选项。

这个表本身并没有那么大(530 万行,1.3 GB 数据)。但是在 23 个引用的表中,有两个表非常大(超过 10 亿行,每行 54 和 69 GB)。

问题是当我们尝试删除“Trips”表中的少量行(假设为 4 行)时,SQL 估计将要删除这么多行,它需要 10GB 的 RAM,估计将有数百万行返回,并锁定表。一切都停止,其他查询阻塞,应用程序超时。

以下是 1 个删除语句的主表和行数:

  • 行程(4 行)
  • 细分(27 行,与 SegmentId 的旅行相关)
  • 配置文件(2012 行,按 SegmentId 与 Segments 相关)
  • ProfileRanges(2337 行,与 ProfileId 的 Profiles 相关)
  • 事件(7750 行,按 SegmentId 与 Segments 相关)
  • EventConditions(9230 行,通过 EventId 与事件相关)

表 EventConditions 和 ProfileRanges 各有超过 10 亿行。

这是计划缓存:https : //www.brentozar.com/pastetheplan/?id=HJNg5I0BU

当我查看 SentryOne 计划资源管理器时,我可以看到 SQL 正在读取整个表,即使“表假脱机”然后过滤并只保留 2012 行 ProfileRanges 和 EventConditions 大致相同。

轮廓范围

TableSpool ProfileRanges

事件条件

TableSpool 事件条件

当我使用 Brent Ozar 的 sp_blitzCache 过程查看查询的内存授予时,我可以看到该查询需要大约 10gb 的 RAM。

记忆授权

之后,查询要么等待 SOS_SCHEDULER_YIEL(因此等待 4 毫秒后轮到它使用 CPU)或 MEMORY_ALLOCATION_EXT。程序超时并失败。

我能做些什么来完成这项工作?

我想到的一件事是删除两个最大表上的外键,并在而不是触发器中删除它们的行。但我不喜欢使用触发器而不是外键来强制执行数据库一致性。

任何建议或帮助将不胜感激


ProfileRanges 的主键是

  • ProfileId int
  • ProfileRangeDefId1 int
  • ProfileRangeDefId2 int

EventConditions 的主键是

  • 事件 ID 大整数
  • EventConditionDefId int

Pau*_*ite 17

假设所有相关表对删除路径都有正确的索引,您可以尝试:

DELETE [Trips]
WHERE [ISAFileName]='ID_774199_20200311_133117.isa'
OPTION (LOOP JOIN, FAST 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
Run Code Online (Sandbox Code Playgroud)

如果可行,请尝试将其减少到最少的提示数。

这些类型的计划对于基数估计非常具有挑战性,并且“默认”CE 模型通常会造成混乱

一旦您有了一个运行良好的计划形状,您应该能够在必要时使用计划指南等强制该形状。


Dav*_*oft 9

级联删除表扫描是表上没有正确索引的常见症状。

确保所有 FK 表都有支持外键的索引。即以 FK 列作为其他表索引中的前导列的聚集或非聚集索引。

例如

create index ix_TripId on EventConditions (TripId)
Run Code Online (Sandbox Code Playgroud)

并考虑 TripID FK 列是否不应该是聚集索引中的前导列,例如:

create table EventConditions
(
  TripId int not null,
  EventId bigint not null,
  EventConditionDefId int not null,
  constraint pk_EventConditions 
     primary key clustered(TripId, EventId, EventConditionDefId),
  ...
)
Run Code Online (Sandbox Code Playgroud)

这将优化每个表以供 TripId 访问。

此外

我想到的一件事是删除两个最大表上的外键,并在而不是触发器中删除它们的行。

您不需要删除 FK。只需先从子表中删除,并可能从 FK 中删除 ON DELETE CASCADE 以要求先删除子表。这将首先加载带有要在每个级别删除的键值的临时表,然后从上到下加载它们。

create table #tripIdsToDelete(TripId int primary key)
insert into #tripIdsToDelete ...

create table #EventIdsToDelete(EventId int primary key)
insert into #EventIdsToDelete(EventID)
  select EventId from Events 
  where TripId in (select TripId from #tripIdsToDelete)
...
create table #EventConditionIdsToDelete ...
Run Code Online (Sandbox Code Playgroud)