针对IN运算符

时间:2016-07-19 15:01:45

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

我编写了一个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字段的所有可能值是什么。我无法找到支持这一点的任何文献,并想知道他是否确实是正确的?

2 个答案:

答案 0 :(得分:2)

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

首先设置一个示例模式:

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);

每个人的计划是:

enter image description here

正如您所看到的,这里存在可忽略的差异,没有索引,两个查询都需要聚簇索引扫描。

如果可能的值很少,非聚集索引不可能有任何用处,除非您将所有列定期包含为非键列,或者没有太多数据。在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的计划变得非常复杂,虽然我不会过分重视其重要性,但估计的成本是相同的:

enter image description here

然而,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;

如本执行计划所示:

enter image description here

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

WHERE   RECONCILIATION_STATUS IN (0, 2);

不仅在性能方面,虽然在大多数情况下看起来很少或根本没有,但肯定在可读性方面和未来的附加值证明方面。

然而,没有更好的方法可以找到自己在自己的数据上运行这些测试。这将使您更好地了解什么比我从一小组数据样本中汇总的假设更好。

答案 1 :(得分:1)

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

我会将此与Reconciliation_Status列上的过滤索引相结合,过滤WHERE Reconcilition_Status != 1。这可能最终导致长期性能提升。

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

相关问题