【问题标题】:T-SQL Performance of != operator against IN operator!= 运算符对 IN 运算符的 T-SQL 性能
【发布时间】:2016-07-19 15:01:45
【问题描述】:

我编写了一个 SQL Server 2008 R2 存储过程来执行协调,并且我有一个协调状态标志 (TINYINT),它的值可以是 0(新)、1(已协调)或 2(异常)。

在此过程中,我使用 != 运算符选择所有尚未成功协调到临时表中的记录:

SELECT FIELDS
INTO #TEMP_TABLE
FROM PERMANENT_TABLE
WHERE RECONCILIATION_STATUS != 1

在与工作中的 DBA 交谈时,他认为将其重新编码为:

SELECT FIELDS
INTO #TEMP_TABLE
FROM PERMANENT_TABLE
WHERE RECONCILIATION_STATUS in (0, 2)

会更高效,因为我们知道 RECONCILIATION_STATUS 字段的所有可能值可以是什么。我找不到任何支持这一点的文献,我想知道他是否真的正确?

【问题讨论】:

  • 您发现执行计划有什么不同吗?
  • 你确定这很重要吗? - 最坏的情况是in 子句中的每个值都必须与候选值进行比较,这与 != 进行对比,这是一个单一的比较。 3 的未来值是多少?
  • SQL Server 也应该能够对不等式进行索引搜索,至少如果您的统计信息是最新的。我会假设只有很小比例的行具有 1 以外的值?
  • 附带说明,如果您还没有过滤索引,则状态字段上的过滤索引可能是个好主意——如果我之前对数据分布的假设是正确的。
  • 如果对帐状态可以超过 0,1,2,那么我认为第二个语句会更快。特别是如果它在该列上被索引。

标签: sql-server performance sql-server-2008-r2 inequality in-operator


【解决方案1】:

显而易见的解决方案是测试两者。

首先设置一个示例架构:

IF OBJECT_ID(N'dbo.T', 'U') IS NOT NULL DROP TABLE dbo.T;
CREATE TABLE dbo.T 
(
    ID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
    RECONCILIATION_STATUS TINYINT NOT NULL CHECK (RECONCILIATION_STATUS IN (0, 1, 2)),
    Filler CHAR(100) NULL
);

INSERT dbo.T (RECONCILIATION_STATUS)
SELECT  TOP (100000) FLOOR(RAND(CHECKSUM(NEWID())) * 3)
FROM    sys.all_objects a, sys.all_objects b;

然后在没有索引的情况下进行测试

SELECT  COUNT(Filler)
FROM    dbo.T
WHERE   RECONCILIATION_STATUS != 1;

SELECT  COUNT(Filler)
FROM    dbo.T
WHERE   RECONCILIATION_STATUS IN (0, 2);

每个人的计划是:

如您所见,这里的差异可以忽略不计,因为没有索引,所以两个查询都需要进行聚集索引扫描。

由于可能的值如此之少,非聚集索引不太可能有任何用处,除非您将定期需要的所有列都包含为非键列,或者没有太多数据。在 100,000 个样本行上使用标准非聚集索引,构建如下:

CREATE NONCLUSTERED INDEX IX_T__RECONCILIATION_STATUS
    ON dbo.T (RECONCILIATION_STATUS);

执行计划与聚集索引扫描保持一致。

将其他列包含为非键索引:

CREATE NONCLUSTERED INDEX IX_T__RECONCILIATION_STATUS
    ON dbo.T (RECONCILIATION_STATUS) INCLUDE (Filler);

!= 1 的计划变得相当复杂,虽然我不会过多强调它的重要性,但估计的成本是一样的:

但是,IO 统计数据表明,实际所需的读取几乎没有什么不同:

表“T”。扫描计数 2,逻辑读取 935,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。

表“T”。扫描计数 2,逻辑读取 934,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。

到目前为止,差别不大,但这实际上取决于您的数据分布,以及您拥有的索引和约束。

有趣的是,如果您为测试创建一个临时表并在其上定义检查约束:

IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL DROP TABLE #T;
CREATE TABLE #T 
(
    ID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
    RECONCILIATION_STATUS TINYINT NOT NULL CHECK (RECONCILIATION_STATUS IN (0, 1, 2)),
    Filler CHAR(100) NULL
);

INSERT #T (RECONCILIATION_STATUS)
SELECT  TOP (100000) FLOOR(RAND(CHECKSUM(NEWID())) * 3)
FROM    sys.all_objects a, sys.all_objects b;

优化器实际上会重写这个查询:

SELECT  COUNT(Filler)
FROM    #T
WHERE   RECONCILIATION_STATUS != 1;

作为

SELECT  COUNT(Filler)
FROM    #T
WHERE   RECONCILIATION_STATUS = 0
OR      RECONCILIATION_STATUS = 2;

如本执行计划所示:

不过,我无法在永久表上复制此行为。尽管如此,这让我相信最好的选择是

WHERE   RECONCILIATION_STATUS IN (0, 2);

不仅在性能方面,尽管在大多数情况下它似乎是微不足道的或根本没有,但在可读性和未来对附加值的验证方面也是如此。

但是,没有比对自己的数据进行此类测试更好的方法来找出答案。这将使您更好地了解什么比我可以从一小部分数据样本集中得出的任何假设表现得更好。

【讨论】:

  • 我很欣赏你指出最好的方法是同时尝试这两种方法......然后你用插图和结果解释做了它。很有帮助。过去,我使用 IN 谓词作为优化某些查询的一种方式,但不确定为什么它的性能稍高一些。谢谢!
  • 谢谢加雷斯。可能是我见过的最彻底的答案之一,并根据后续计划显示了赤裸裸的事实。似乎在这种情况下,可能对附加值的未来证明将是选择一个选项而不是另一个选项的主要理由。按照 JamesZ 的建议添加过滤索引也应该对我有所帮助。
【解决方案2】:

Alex K 在评论中提到,使用in 子句需要对每个值进行两次比较,而使用!= 只是一次。所以从表面上看,这将使单一价值解决方案更具吸引力。

我会将此与过滤WHERE Reconcilition_Status != 1Reconciliation_Status 列上的过滤索引结合使用。从长远来看,这可能最终会带来更多的性能提升。

要考虑的另一件事是代码的可维护性。如果将来有可能在此列中允许更多值,那么使用in 解决方案可能会在查询未更新的情况下立即使结果无效(因为如果您将3 添加为新值,in (0,2) 过滤器将排除具有 3 的行,而 != 1 仍将返回假定的所需结果。

【讨论】:

  • 我同意 Yaakov,过滤索引将(当前)提高性能,并且您在“!= 1”上的观点将始终给出所需的结果在当前的协调上下文中是正确的。但当然,这在未来可能会发生变化(不太可能),并且必须相应地审查、更新和测试代码。
猜你喜欢
  • 2021-08-21
  • 2011-09-21
  • 1970-01-01
  • 2021-10-24
  • 1970-01-01
  • 2011-07-21
  • 1970-01-01
  • 2013-05-19
  • 1970-01-01
相关资源
最近更新 更多