如何优化存储过程并减少表扫描?

Ris*_*osh 2 sql-server sql-server-2012 query-performance

我有一个存储过程,可以识别上次 SSIS 作业运行中候选人信息的任何更改。然后它继续将更改保存在同步表中。

\n
CREATE PROCEDURE [dbo].[IdentifyCandidateChanges]\xc2\xa0\xc2\xa0\n(\xc2\xa0\xc2\xa0\n@MoreToProcess BIT = 0 OUTPUT\xc2\xa0\xc2\xa0\n)\xc2\xa0\xc2\xa0\nAS\xc2\xa0\xc2\xa0\n\nSET NOCOUNT ON\xc2\xa0\xc2\xa0\n\nDECLARE @BatchSize INT\xc2\xa0\xc2\xa0\n\nSELECT\xc2\xa0\xc2\xa0\n@MoreToProcess = 0\xc2\xa0\xc2\xa0\n, @BatchSize = 200000\xc2\xa0\xc2\xa0\n\nDECLARE @Changes TABLE\xc2\xa0\xc2\xa0\n(\xc2\xa0\xc2\xa0\n  CandidateNo VARCHAR(10) NOT NULL\xc2\xa0\xc2\xa0\n, CentreNo CHAR(5) NOT NULL\xc2\xa0\xc2\xa0\n, CandidateModifiedDate DATETIME NOT NULL\xc2\xa0\xc2\xa0\n, ChangeStatus CHAR(1) NOT NULL\xc2\xa0\xc2\xa0\n)\xc2\xa0\xc2\xa0\n\n-- If this is the first run (or the CandidateSync table is empty for another reason) then just take the hit now\xc2\xa0\xc2\xa0\n-- and process the entire set of candidates in one go.\xc2\xa0\xc2\xa0\n\nIF EXISTS(SELECT TOP 1 1 FROM CandidateSync)\xc2\xa0\xc2\xa0\nSET ROWCOUNT @BatchSize\xc2\xa0\xc2\xa0\n\nINSERT INTO @Changes\xc2\xa0\xc2\xa0\n(\xc2\xa0\xc2\xa0\n  CandidateNo\xc2\xa0\xc2\xa0\n, CentreNo\xc2\xa0\xc2\xa0\n, CandidateModifiedDate\xc2\xa0\xc2\xa0\n, ChangeStatus\xc2\xa0\xc2\xa0\n)\xc2\xa0\xc2\xa0\nSELECT\xc2\xa0\xc2\xa0\nCOALESCE(RTRIM(Known.CandidateNo), CandidateSync.CandidateNo) AS CandidateNo\xc2\xa0\xc2\xa0\n, COALESCE(Known.CentreNo, CandidateSync.CentreNo) AS CentreNo\xc2\xa0\xc2\xa0\n, COALESCE(Candidate.ModifiedDate, CandidateSync.CandidateModifiedDate) AS CandidateModifiedDate\xc2\xa0\xc2\xa0\n, CASE\xc2\xa0\xc2\xa0\xc2\xa0\n\xc2\xa0 WHEN CandidateSync.CandidateNo IS NULL THEN\xc2\xa0\xc2\xa0\xc2\xa0\n\xc2\xa0\xc2\xa0 \'I\'\xc2\xa0\xc2\xa0\xc2\xa0\n\xc2\xa0 WHEN Known.CandidateNo IS NULL THEN\xc2\xa0\xc2\xa0\xc2\xa0\n\xc2\xa0\xc2\xa0 \'D\'\xc2\xa0\xc2\xa0\xc2\xa0\n\xc2\xa0 ELSE\xc2\xa0\xc2\xa0\xc2\xa0\n\xc2\xa0\xc2\xa0 \'U\'\xc2\xa0\xc2\xa0\xc2\xa0\nEND AS ChangeStatus\xc2\xa0\xc2\xa0\nFROM\xc2\xa0\xc2\xa0\nCandidate WITH(INDEX(IX_Candidate))\xc2\xa0\xc2\xa0\nINNER MERGE JOIN\xc2\xa0\xc2\xa0\n(\xc2\xa0\xc2\xa0\nSELECT\xc2\xa0\xc2\xa0\n\xc2\xa0 Registration.CandidateNo\xc2\xa0\xc2\xa0\n, Registration.CentreNo\xc2\xa0\xc2\xa0\nFROM\xc2\xa0\xc2\xa0\n\xc2\xa0 Registration WITH(INDEX(IX_Registration))\xc2\xa0\xc2\xa0\nUNION SELECT\xc2\xa0\xc2\xa0\n\xc2\xa0 AssessHeaderValid.CandidateNo\xc2\xa0\xc2\xa0\n, AssessHeaderValid.CentreNo\xc2\xa0\xc2\xa0\nFROM\xc2\xa0\xc2\xa0\n\xc2\xa0 AssessHeaderValid WITH(INDEX(IX_AssessHeaderValid))\xc2\xa0\xc2\xa0\nWHERE\xc2\xa0\xc2\xa0\n\xc2\xa0 AssessHeaderValid.Deleted = 0\xc2\xa0\xc2\xa0\nUNION SELECT\xc2\xa0\xc2\xa0\n\xc2\xa0 CandAnsHeaderValid.CandidateNo\xc2\xa0\xc2\xa0\n, CandAnsHeaderValid.CentreNo\xc2\xa0\xc2\xa0\nFROM\xc2\xa0\xc2\xa0\n\xc2\xa0 CandAnsHeaderValid WITH(INDEX(IX_CandAnsHeaderValid))\xc2\xa0\xc2\xa0\n) AS Known\xc2\xa0\xc2\xa0\nON\xc2\xa0\xc2\xa0\nKnown.CandidateNo = Candidate.CandidateNo\xc2\xa0\xc2\xa0\nFULL OUTER MERGE JOIN\xc2\xa0\xc2\xa0\nCandidateSync WITH(INDEX(PK_CandidateSync))\xc2\xa0\xc2\xa0\nON\xc2\xa0\xc2\xa0\nCandidateSync.CandidateNo = Known.CandidateNo\xc2\xa0\xc2\xa0\nAND CandidateSync.CentreNo = Known.CentreNo\xc2\xa0\xc2\xa0\nWHERE\xc2\xa0\xc2\xa0\nCandidateSync.CandidateNo IS NULL\xc2\xa0\xc2\xa0\nOR Known.CandidateNo IS NULL\xc2\xa0\xc2\xa0\nOR CandidateSync.CandidateModifiedDate < Candidate.ModifiedDate\xc2\xa0\xc2\xa0\nOR CandidateSync.ChangeStatus = \'D\'\xc2\xa0\xc2\xa0\nOPTION\xc2\xa0\xc2\xa0\n(\xc2\xa0\xc2\xa0\nMERGE UNION\xc2\xa0\xc2\xa0\n)\xc2\xa0\xc2\xa0\n\nIF @@ROWCOUNT = @BatchSize\xc2\xa0\xc2\xa0\nSET @MoreToProcess = 1\xc2\xa0\xc2\xa0\n\nSET ROWCOUNT 0\xc2\xa0\xc2\xa0\n\nINSERT INTO CandidateSync WITH (PAGLOCK)\xc2\xa0\xc2\xa0\n(\xc2\xa0\xc2\xa0\nCandidateNo\xc2\xa0\xc2\xa0\n, CentreNo\xc2\xa0\xc2\xa0\n, CandidateModifiedDate\xc2\xa0\xc2\xa0\n, ChangeStatus\xc2\xa0\xc2\xa0\n)\xc2\xa0\xc2\xa0\nSELECT\xc2\xa0\xc2\xa0\nChanges.CandidateNo\xc2\xa0\xc2\xa0\n, Changes.CentreNo\xc2\xa0\xc2\xa0\n, Changes.CandidateModifiedDate\xc2\xa0\xc2\xa0\n, CASE Changes.ChangeStatus WHEN \'D\' THEN \'D\' ELSE \'U\' END\xc2\xa0\xc2\xa0\nFROM\xc2\xa0\xc2\xa0\n@Changes Changes\xc2\xa0\xc2\xa0\nWHERE\xc2\xa0\xc2\xa0\nChanges.ChangeStatus = \'I\'\xc2\xa0\xc2\xa0\n\nUPDATE\xc2\xa0\xc2\xa0\nCandidateSync\xc2\xa0\xc2\xa0\nWITH\xc2\xa0\xc2\xa0\n(ROWLOCK)\xc2\xa0\xc2\xa0\nSET\xc2\xa0\xc2\xa0\nCandidateModifiedDate = Changes.CandidateModifiedDate\xc2\xa0\xc2\xa0\n, ChangeStatus = CASE Changes.ChangeStatus WHEN \'D\' THEN \'D\' ELSE \'U\' END\xc2\xa0\xc2\xa0\n, ProcessingStatus = \'U\'\xc2\xa0\xc2\xa0\nFROM\xc2\xa0\xc2\xa0\n@Changes Changes\xc2\xa0\xc2\xa0\nWHERE\xc2\xa0\xc2\xa0\nCandidateSync.CandidateNo = Changes.CandidateNo\xc2\xa0\xc2\xa0\nAND CandidateSync.CentreNo = Changes.CentreNo\xc2\xa0\xc2\xa0\nAND CandidateSync.ProcessingStatus = \'S\'\xc2\xa0\xc2\xa0\nAND Changes.ChangeStatus <> \'I\'\xc2\xa0\xc2\xa0\n
Run Code Online (Sandbox Code Playgroud)\n

索引设置为:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n \n\n\n\n\n\n\n\n\n\n
姓名类型按键
IX_Candidate\xc2\xa0\xc2\xa0\xc2\xa0非集群位于 PRIMARY\xc2\xa0\xc2\xa0\xc2\xa0 候选编号,修改日期
IX_Registration\xc2\xa0\xc2\xa0\xc2\xa0 非集群位于 PRIMARY\xc2\xa0\xc2\xa0\xc2\xa0 候选人编号,中心编号
IX_AssessHeaderValid\xc2\xa0\xc2\xa0\xc2\xa0 非集群位于 PRIMARY\xc2\xa0\xc2\xa0\xc2\xa0 候选人编号、中心编号、已删除
IX_CandAnsHeaderValid\xc2\xa0\xc2\xa0\xc2\xa0 非集群位于 PRIMARY\xc2\xa0\xc2\xa0\xc2\xa0 候选人编号,中心编号
PK_CandidateSync\xc2\xa0\xc2\xa0\xc2\xa0 聚集、唯一、主键位于 PRIMARY\xc2\xa0\xc2\xa0\xc2\xa0 候选人编号,中心编号
\n
\n

表 Candidate、Registration、CandAnsHeaderValid 和 AssessHeaderValid 每天由 SSIS 作业运行更新两次,需要同步到 CandidateSync 表,因为它保存已处理的记录的记录。

\n

问题是该查询需要近一分钟才能完成。有什么办法可以减轻服务器的负载吗?

\n

Eri*_*ing 6

问题1

这里发生了一些事情,但进行所有表扫描的主要原因是,除了 之外AssessHeaderValid.Deleted = 0,您没有一个能够在首次读取数据时评估的 where 子句。

最后的 where 子句由一系列OR评估 CandidateSync 和 Candidate 之间的结果的谓词组成FULL OUTER MERGE JOIN

WHERE  
   CandidateSync.RSACandidateNo IS NULL  
OR Known.RSACandidateNo IS NULL  
OR CandidateSync.CandidateModifiedDate < Candidate.ModifiedDate  
OR CandidateSync.ChangeStatus = 'D' 
Run Code Online (Sandbox Code Playgroud)

这会产生一个如下所示的计划:

坚果

请注意 Filter 运算符的详细信息。由于这是外连接,因此在消除行之前必须完全显示结果。

问题2

第二个问题是您使用的是@table变量,这意味着您无法获得并行执行计划。SQL Server 不支持对@table 变量进行并行修改

#temp 表没有这个限制。在这里获得一个并行计划会很有用,这样您就可以将大量的行分散到其他工作人员中。

问题3

你已经把这件事暗示得要死了。与哈希操作相比,您更喜欢合并操作的原因并不明显,但在这种情况下,能够探索哈希操作的并行计划可能会更好地提高性能。我这么说只是因为并行计划中的合并操作可能会导致真正的性能问题

修复

为了容易做到,以下是我要更改和测试的内容:

  1. 使用#temp 表而不是@table 变量
  2. 删除所有索引和连接提示
  3. 使用EXISTS/NOT EXISTS代替多个OR谓词的连接
  4. 如果这些不能解决所有问题,请将Known派生连接分离到 #temp 表中

关于第三项,请参阅我的文章: