为什么将 where 语句更改为变量会导致查询速度慢 4 倍

Why changing where statement to a variable cause query to be 4 times slower

我正在将数据从“Recovery”数据库中的一个 table“标签”插入另一个 table“R3”数据库中的“标签”

它们都在我的笔记本电脑中,类似 SQL 服务器实例

我已经构建了插入查询,因为 Recovery..Tags table 大约有 180M 条记录,所以我决定将其分成更小的 sebset。 (当时有 100 万条记录)

这是我的查询(我们称之为查询 A)

insert into R3..Tags (iID,DT,RepID,Tag,xmiID,iBegin,iEnd,Confidence,Polarity,Uncertainty,Conditional,Generic,HistoryOf,CodingScheme,Code,CUI,TUI,PreferredText,ValueBegin,ValueEnd,Value,Deleted,sKey,RepType)
SELECT T.iID,T.DT,T.RepID,T.Tag,T.xmiID,T.iBegin,T.iEnd,T.Confidence,T.Polarity,T.Uncertainty,T.Conditional,T.Generic,T.HistoryOf,T.CodingScheme,T.Code,T.CUI,T.TUI,T.PreferredText,T.ValueBegin,T.ValueEnd,T.Value,T.Deleted,T.sKey,R.RepType
FROM Recovery..tags  T inner join Recovery..Reps R on T.RepID = R.RepID
where T.iID between 13000001 and 14000000

大约需要 2 分钟。

没关系

让事情对我来说更容易一些

我把were语句中的iiD放在一个变量中

所以我的查询看起来像这样(我们称之为查询 B)

declare @i int = 12

insert into R3..Tags (iID,DT,RepID,Tag,xmiID,iBegin,iEnd,Confidence,Polarity,Uncertainty,Conditional,Generic,HistoryOf,CodingScheme,Code,CUI,TUI,PreferredText,ValueBegin,ValueEnd,Value,Deleted,sKey,RepType)
SELECT T.iID,T.DT,T.RepID,T.Tag,T.xmiID,T.iBegin,T.iEnd,T.Confidence,T.Polarity,T.Uncertainty,T.Conditional,T.Generic,T.HistoryOf,T.CodingScheme,T.Code,T.CUI,T.TUI,T.PreferredText,T.ValueBegin,T.ValueEnd,T.Value,T.Deleted,T.sKey,R.RepType
FROM Recovery..tags  T inner join Recovery..Reps R on T.RepID = R.RepID
where T.iID between (1000000 * @i) + 1 and (@i+1)*1000000

但这会导致插入变得如此缓慢(大约 10 分钟)

所以我再次尝试查询 A 并给了我大约 2 分钟

我再次尝试查询 B 并给出了大约 8 分钟!!

我正在为每个附加执行计划(在显示查询计划分析的站点上)- Query A Plan and Query B Plan

知道为什么会这样吗? 以及如何修复它?

第一个查询快得多的原因是它是并行的。这意味着基数估计器对它必须处理的数据有足够的了解,并且查询足够大以达到并行执行的阈值。然后,引擎将数据块传递给不同的处理器以单独处理,然后返回报告并重新分区流。

将值作为变量,它实际上变成了标量函数求值,查询不能与标量函数并行,因为值必须在基数之前确定估算器可以弄清楚如何处理它。因此,它在单线程中运行,速度较慢。

某种循环机制可能会有所帮助。创建包含索引以协助引擎处理此请求。您可能会找到更好的循环机制,因为您熟悉您关心的身份范围,但这应该会让您朝着正确的方向前进。根据您的需要进行调整。

使用这样的循环,它会在每个循环中提交更改,因此您不会无限期地锁定 table。

USE Recovery;
GO

CREATE INDEX NCI_iID
ON Tags (iID)
INCLUDE (
            DT
            ,RepID
            ,tag
            ,xmiID
            ,iBegin
            ,iEnd
            ,Confidence
            ,Polarity
            ,Uncertainty
            ,Conditional
            ,Generic
            ,HistoryOf
            ,CodingScheme
            ,Code
            ,CUI
            ,TUI
            ,PreferredText
            ,ValueBegin
            ,ValueEnd
            ,value
            ,Deleted
            ,sKey
        );
GO

CREATE INDEX NCI_RepID ON Reps (RepID) INCLUDE (RepType);

USE R3;
GO

CREATE INDEX NCI_iID ON Tags (iID);
GO

DECLARE @RowsToProcess  BIGINT
        ,@StepIncrement INT = 1000000;

SELECT  @RowsToProcess = (
                             SELECT COUNT(1)
                             FROM   Recovery..tags AS T
                             WHERE  NOT EXISTS (
                                                   SELECT   1
                                                   FROM     R3..Tags AS rt
                                                   WHERE    T.iID = rt.iID
                                               )
                         );

WHILE @RowsToProcess > 0
BEGIN
    INSERT INTO R3..Tags
    (
        iID
        ,DT
        ,RepID
        ,Tag
        ,xmiID
        ,iBegin
        ,iEnd
        ,Confidence
        ,Polarity
        ,Uncertainty
        ,Conditional
        ,Generic
        ,HistoryOf
        ,CodingScheme
        ,Code
        ,CUI
        ,TUI
        ,PreferredText
        ,ValueBegin
        ,ValueEnd
        ,Value
        ,Deleted
        ,sKey
        ,RepType
    )
    SELECT  TOP (@StepIncrement)
            T.iID
            ,T.DT
            ,T.RepID
            ,T.Tag
            ,T.xmiID
            ,T.iBegin
            ,T.iEnd
            ,T.Confidence
            ,T.Polarity
            ,T.Uncertainty
            ,T.Conditional
            ,T.Generic
            ,T.HistoryOf
            ,T.CodingScheme
            ,T.Code
            ,T.CUI
            ,T.TUI
            ,T.PreferredText
            ,T.ValueBegin
            ,T.ValueEnd
            ,T.Value
            ,T.Deleted
            ,T.sKey
            ,R.RepType
    FROM    Recovery..tags AS T
            INNER JOIN Recovery..Reps AS R ON T.RepID = R.RepID
    WHERE   NOT EXISTS (
                           SELECT   1
                           FROM     R3..Tags AS rt
                           WHERE    T.iID = rt.iID
                       )
    ORDER BY
            T.iID;

    SET @RowsToProcess = @RowsToProcess - @StepIncrement;
END;

时间上的巨大差异是由于为加入标签和代表而制定的非常不同的计划。

从根本上说,在版本 A 中,它知道要提取多少数据(一百万行),并且可以为此设计一个高效的查询。但是,因为您在 B 中使用变量来定义要导入的数据量,所以它必须定义一个更通用的查询 - 一个适用于 10 行、100 万行或 1 亿行的查询。

在计划中,这里是加入标签和代表的查询的相关部分...

...在A

... 和 B

请注意,在 A 中执行连接只需要一分多钟; B区需要6分半钟。

看起来很花时间的关键是它对标签 table 进行了 table 扫描,这需要 5:44 才能完成。该计划将此作为 table 扫描,因为下次您 运行 查询时,您可能需要超过 100 万行。

第二个问题是它从 Reps 读取(或期望读取)的数据量也不正常。在 A 中,它预计读取 200 万行并读取 1421;在 B 中,它基本上全部读取了它们(尽管从技术上讲它可能只需要相同的 1421)。

我认为您有两种主要的修复方法

  • 查看索引,删除标签上的 table 扫描 - 确保索引匹配所需内容并允许查询对该索引进行扫描(看起来 @ 顶部的索引MikePetri 的回答是你需要的,或类似的)。这种方式不是进行 table 扫描,而是可以进行索引扫描,该扫描可以从数据集的 'in the middle' 开始(table 扫描必须从数据的开头或结尾开始设置).
  • 将其分为两个过程。第一个进程从 Tags 中获取相关的百万行,并将其保存在临时 table 中。第二个进程使用临时 table 中的数据加入 Reps(也尝试在第二个查询中使用 option (recompile),以便它在创建计划之前检查临时 table 的大小).

您甚至可以在该临时 table 上放置一两个索引(and/or 主键)以使其更好地用于下一步。