SQL中的“不是”比“SQL”更昂贵吗?
除了可能在表中的行数之外,这些示例查询之一是否比另一个更昂贵?
SELECT * FROM dbo.Accounts WHERE AccountID IN (4,6,7,9,10) SELECT * FROM dbo.Accounts WHERE AccountID NOT IN (4,10) 解决方法一般来说,NOT IN将会更昂贵,但是当然可以构建相反的情况.首先,假设AccountId是Accounts表的主键. IN(4,10)将需要5个索引搜索,这意味着逻辑IO是索引的深度(每个搜索需要从根向下通过中间页面到正好一个叶页面) . NOT IN(4,10)将需要一个完整的扫描和一个过滤器(可推式不可谓的谓词意味着它被推入扫描而不是一个单独的操作符),这意味着逻辑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,10) /* Scan count 5,logical reads 10*/ SELECT * FROM #Accounts WHERE AccountID NOT IN (4,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,'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,10) /*Scan count 5,logical reads 378*/ SELECT * FROM #Accounts WHERE AccountID NOT IN (4,10) /*Scan count 2,logical reads 295*/ SELECT index_depth,OBJECT_ID('tempdb..#Accounts'),'DETAILED') 返回 index_depth page_count ----------- -------------------- 3 358 3 2 3 1 (当聚合索引关键字中添加了唯一数组时,该索引将被替换) 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)(与统计资料中的378份相比) 而对于NOT IN版本: 寻求< 4访问1根页面,1中间页和1叶页(3) 寻求> 4次访问1个根页面,1个中间页面和287页页面(289) 总计:(292)(对比在295个统计数据中报告的295个) 那么IO的下落不明? 事实证明这些与预读机制有关.可以(在开发实例上)使用跟踪标志来禁用此机制,并验证逻辑读取是否按上述描述按预期方式进行报告. This is discussed further in the comments to this blog post. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |