SQL'不是'比''更''昂贵'吗?

时间:2011-05-04 21:56:35

标签: sql sql-server performance

除了表中可能包含的行数之外,这些示例查询中的一个会比另一个更昂贵吗?

SELECT * FROM dbo.Accounts WHERE AccountID IN (4,6,7,9,10) 

SELECT * FROM dbo.Accounts WHERE AccountID NOT IN (4,6,7,9,10)

3 个答案:

答案 0 :(得分:17)

一般来说,NOT IN会更贵,尽管可能会构建相反的情景。

首先,假设AccountIdAccounts表的主键。

IN (4,6,7,9,10)将需要5个索引搜索,这意味着逻辑IO是索引深度的5 *(每个搜索需要从根目录向下导航到中间页面,然后到达一个叶子页面)。

NOT IN (4,6,7,9,10)将需要完整扫描和过滤器(可推送的非sargable谓词意味着它被推入扫描而不是作为单独的运算符),这意味着逻辑IO将等于叶节点中的页数索引+非叶级别的数量。

要看到这个

CREATE  TABLE #Accounts
(
AccountID INT IDENTITY(1,1) PRIMARY KEY,
Filler CHAR(1000)
)
INSERT INTO #Accounts(Filler)
SELECT 'A'
FROM master..spt_values

SET STATISTICS IO ON


SELECT * FROM #Accounts WHERE AccountID IN (4,6,7,9,10) 
/* Scan count 5, logical reads 10*/

SELECT * FROM #Accounts WHERE AccountID NOT IN (4,6,7,9,10)
/*Scan count 1, logical reads 359*/

SELECT index_depth, page_count
FROM
sys.dm_db_index_physical_stats (2,object_id('tempdb..#Accounts')
                                     , DEFAULT,DEFAULT, 'DETAILED')

返回

index_depth page_count
----------- --------------------
2           358
2           1

查看病态不同的情况,其中所有行符合IN子句,因此NOT IN

SET STATISTICS IO OFF


CREATE  TABLE #Accounts
(
AccountID INT ,
Filler CHAR(1000)
)

CREATE CLUSTERED INDEX ix ON #Accounts(AccountID)

;WITH Top500 AS
(
SELECT TOP 500 * FROM master..spt_values
), Vals(C) AS
(
SELECT 4 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 9 UNION ALL
SELECT 10
)

INSERT INTO #Accounts(AccountID)
SELECT C
FROM Top500, Vals

SET STATISTICS IO ON

SELECT * FROM #Accounts WHERE AccountID IN (4,6,7,9,10) 
/*Scan count 5, logical reads 378*/

SELECT * FROM #Accounts WHERE AccountID NOT IN (4,6,7,9,10)
/*Scan count 2, logical reads 295*/

SELECT index_depth,page_count
FROM
sys.dm_db_index_physical_stats (2,OBJECT_ID('tempdb..#Accounts'), DEFAULT,DEFAULT, 'DETAILED')

返回

index_depth page_count
----------- --------------------
3           358
3           2
3           1

(索引是deper,因为uniquifier已添加到聚簇索引键中)

IN仍然实现为5次相等搜索,但这次在每次搜索时读取的叶子页数远远超过1.叶子页面排列在一个链表中,SQL Server继续导航直到遇到与搜索不匹配的行。

NOT IN现在实现为2范围搜索

[1] Seek Keys[1]: END: #Accounts.AccountID < Scalar Operator((4)), 
[2] Seek Keys[1]: START: #Accounts.AccountID > Scalar Operator((4))  

使用残差谓词

WHERE  ( #Accounts.AccountID < 6 
          OR #Accounts.AccountID > 6 ) 
       AND ( #Accounts.AccountID < 7 
              OR #Accounts.AccountID > 7 ) 
       AND ( #Accounts.AccountID < 9 
              OR #Accounts.AccountID > 9 ) 
       AND ( #Accounts.AccountID < 10 
              OR #Accounts.AccountID > 10 )  

因此可以看出,即使在这种极端情况下,最好的SQL Server也可以跳过仅查看其中一个NOT IN值的叶页。有点令人惊讶的是,即使我倾斜分布使得AccountID=7记录比AccountID=4记录的普遍性高6倍,它仍然给出相同的计划,并且没有重写它,因为范围寻求7的任何一方,类似地,当将AccountID=4记录的数量减少到1时,计划恢复为聚簇索引扫描,因此似乎仅限于将此转换仅考虑到索引中的第一个值。

加成

在我的答案的前半部分,数字与我的描述和索引深度完全一致。

在第二部分中,我的回答没有解释为什么一个具有3个级别和358个叶子页面的索引应该导致它完全具有逻辑读取次数,这是因为我不太确定自己!但是我现在已经填补了所缺少的知识。

首先是此查询(仅限SQL Server 2008+语法)

SELECT AccountID, COUNT(DISTINCT P.page_id) AS NumPages
FROM #Accounts
CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%) P
GROUP BY AccountID
ORDER BY AccountID

给出这些结果

AccountID   NumPages
----------- -----------
4           72
6           72
7           73
9           72
10          73

加起来NumPages总共有362个反映了一些叶子页面包含2个不同AccountId值的事实。这些页面将被搜索访问两次。

SELECT COUNT(DISTINCT P.page_id) AS NumPages
FROM #Accounts
CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%) P
WHERE AccountID <> 4

给出

NumPages
-----------
287

所以,

对于IN版本:

寻求=4次访问1个根页,1个中间页和72个叶页(74)

寻求=6次访问1个根页,1个中间页和72个叶页(74)

寻求=7次访问1个根页,1个中间页和73个叶页(75)

寻求=9次访问1个根页,1个中间页和72个叶页(74)

寻求=10次访问1个根页,1个中间页和73个叶页(75)

总计:(372)(与IO统计数据中报告的378相比)

并且,对于NOT IN版本:

寻找<4次访问1个根页,1个中间页和1个叶页(3)

寻求>4次访问1个根页,1个中间页和287个叶页(289)

总计:(292)(与IO统计数据中报告的295相比)

那么IO的解决方案呢?

事实证明,这些与read-ahead机制有关。 (在开发实例上)可以使用跟踪标志来禁用此机制,并验证逻辑读取现在按照上面的描述按预期报告。 This is discussed further in the comments to this blog post.

答案 1 :(得分:8)

NOT IN基本上意味着全表扫描 - 大部分时间。 异常是指索引和索引分布不佳(索引中的值很少)且大多数值都处于NOT IN条件。

如果您没有索引,则

IN也表示全表扫描。有了索引,它将主要使用索引。另外一个例外是分布不佳的索引或表中的行数很少,全表扫描速度更快。

答案 2 :(得分:0)

不同的DBMS甚至同一DBMS的不同版本可能会因细节而异。 在这些情况下,传统的教科书算法表示IN将优于NOT IN: 1.列上有B +树索引。 2.选择性低。 (对于IN中列出的值) 在实践中,马丁的解决方案是首选:我们应该衡量实际系统。

相关问题