当目标表有聚集索引时插入速度要慢得多

Sir*_*lot 6 sql-server clustered-index sql-server-2016

我刚刚调试了一个让我完全困惑的问题。

我们的开发数据仓库上的 ETL 转换流程在每天成功运行数月后刚刚失败。使用相同的表架构、索引和数据调用相同存储过程的相同 SSIS 作业在生产中工作正常。

此步骤通常需要不到 2 分钟。今天,在 4 小时后,工作尚未完成,但也没有失败。没有报告的错误。SQL 日志sp_who2中没有任何内容,也没有显示任何阻塞。

  • 是查询执行良好时的估计计划的链接。
  • 是查询未完成时估计计划的链接。

该作业会截断临时表,然后插入大约 600,000 行数据。ETL 进程具有对该表的独占访问权。当我检查时,我只能看到等待CXPACKET

我已将故障追溯到唯一的聚集索引。

该表在标识列上有一个非聚集主键(见下文)

CREATE TABLE [dbo].[Transform_JobCosting_Transaction](
    [ETL_TransformKey] [int] IDENTITY(1,1) NOT NULL,
    [TransactionId] [varchar](255) NOT NULL,
    [KeyType] [varchar](255) NOT NULL,
    [FinancialYear] [varchar](255) NOT NULL,
    [Job] [varchar](255) NOT NULL,
    [Subjob] [varchar](255) NOT NULL,
    [AnalysisCode] [varchar](255) NULL,
    [etc] [varchar](255) NOT NULL,
    [etc] [varchar](255) NOT NULL,
    [etc] [varchar](255) NOT NULL
     CONSTRAINT [PK_Transform_JobCosting_Transaction] PRIMARY KEY NONCLUSTERED 
(
    [ETL_TransformKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)

问题聚集索引是:

CREATE UNIQUE CLUSTERED INDEX [IDX_Unique] ON [dbo].[Transform_JobCosting_Transaction]
(   [FinancialYear] ASC,
    [KeyType] ASC,
    [TransactionId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
Run Code Online (Sandbox Code Playgroud)

有第三个非聚集索引,它根本不影响插入。

我们两个人已经为此工作了 4 个小时。我已经删除并重新添加索引 20-30 次尝试不同组合的选项。

总结:聚集索引块插入。非集群工作正常。

我们已经尝试过:

  • 服务器重启没有区别。
  • 表截断和 sp 从 SSMS 手动运行无差异。(使用 SA 权限)
  • 重建索引没有帮助。
  • 如果我删除聚集索引,则插入工作。
  • 在上面插入后,我可以添加聚集索引而不会出错。
  • 如果将索引删除并重新添加为非聚集索引,则它可以工作。
  • 我已经检查了数据,它是按这 3 个字段分组的唯一数据。
  • 更改索引使其不唯一没有任何区别。
  • 添加/删除with tablock提示没有帮助。
  • 我尝试在插入之前对数据进行排序,但没有任何区别。

运行:Microsoft SQL Server 2016 (SP1-CU2) (KB4013106) - Windows Server 2012 R2 Standard 6.3 上的 13.0.4422.0 (X64) Developer Edition(64 位)

任何想法或建议将不胜感激。

Joe*_*ish 7

让我们退后一步,忘记所有围绕聚集索引的故障排除。您有一个INSERT查询,它过去可以在合理的时间内完成,但现在几小时后就无法完成。为什么该查询现在会很慢?我们来看看预估的计划:

估计计划不好

从右往左看,计划是先从 扫描单行Extract_DW_Control_FinanceExtract_JCS_Trans在内侧做一个扫描的循环连接,根据目标表的聚簇键对数据进行排序,再用一个循环连接扫描Extract_GL_Jnl_Trans内侧。第一次加入可能不是问题。该计划实际上不能从并行性中受益,但是对于外部结果集中的一行,扫描Extract_JCS_Trans应该只发生一次。但是,优化器估计该连接中将出现一行。如果该行估计是错误的,那么您最终可能会在Extract_GL_Jnl_Trans.

执行良好的查询的查询计划使用不同的策略。行估计明显不同,它执行散列连接:

查询计划好查询

我怀疑如果您修复行估计,优化器会为性能不佳的查询选择不同的计划。如果Extract_DW_Control_Finance表总是只有一行,您可以考虑将其移动到局部变量中并可能使用RECOMPILE提示。这可能会导致更好的估计。

至于为什么删除聚集索引会导致问题,我怀疑优化器会在Extract_GL_Jnl_Trans没有聚集索引的情况下进行哈希连接。散列连接不会保留外部输入的顺序,但循环连接会保留顺序。优化器对单行进行排序和执行循环连接的成本可能低于对 356566 行进行哈希连接并稍后执行排序的成本。但是,如果不需要排序,那么进行散列连接的成本可能低于循环连接。这可能都归结为修复您的基数估计。

如果您需要在慢查询运行时进行更多故障排除,并且您使用的是 SQL Server 2016 SP1 ,则可以考虑使用跟踪标志 7412。这应该为您提供有关 SQL Server 在查询计划中“卡住”的位置的线索。如果您能够要求实际计划或直接在 SSMS 中运行查询,您可以使用 sys.dm_exec_query_profiles 或实时查询统计功能。