Sql server 非唯一聚集索引上搜索的逻辑读取

Sql server 非唯一聚集索引上搜索的逻辑读取,sql-server,performance,indexing,Sql Server,Performance,Indexing,对于表定义 CREATE TABLE Accounts ( AccountID INT , Filler CHAR(1000) ) 包含21行(每个AccountId值有7行4,6,7) 它有1个根页面和4个叶页面 index_depth page_count index_level ----------- -------------------- ----------- 2 4 0 2 1

对于表定义

CREATE  TABLE Accounts
(
AccountID INT ,
Filler CHAR(1000)
)
包含21行(每个AccountId值有7行
4,6,7

它有1个根页面和4个叶页面

index_depth page_count           index_level
----------- -------------------- -----------
2           4                    0
2           1                    1
根页面看起来像

FileId      PageId      ROW         LEVEL       ChildFieldId ChildPageId AccountId (KEY) UNIQUIFIER (KEY) KeyHashValue
----------- ----------- ----------- ----------- ------------ ----------- --------------- ---------------- ------------------------------
1           121         0           1           1            119         NULL            NULL             NULL
1           121         1           1           1            151         6               0                NULL
1           121         2           1           1            175         6               3                NULL
1           121         3           1           1            215         7               1                NULL
AccountId
记录在这些页面上的实际分布为

AccountID   page_id     Num
----------- ----------- -----------
4           119         7
6           151         3
6           175         4
7           175         1
7           215         6
询问

SELECT AccountID 
FROM Accounts 
WHERE AccountID IN (4,6,7) 
提供以下IO统计信息

Table 'Accounts'. Scan count 3, logical reads 13
为什么?

我认为对于每一次搜索,它都会搜索可能包含该值的第一页,然后(如果必要的话)直到发现第一行不等于搜索的值

然而,这只会增加10次页面访问

4)  Root Page -> Page 119 -> Page 151             (Page 151 Contains a 6 so should stop)
6)  Root Page -> Page 119 -> Page 151 -> Page 175 (Page 175 Contains a 7 so should stop)
7)  Root Page -> Page 175 -> Page 215             (No more pages)      
那么,是什么导致了额外的3个呢

要复制的完整脚本
从我从
DBCC IND
的输出中看到,有1个根页面(
type=10
),1个关键页面(
type=2
)和4个叶页面(
type=1
),总共有
6个
页面


因此,每次扫描都作为
root->key->leaf->…->final leaf
,其中
4
7
读取4次,
6
读取5次,总计
4+4+5=13
,只是为了以答案的形式提供答案,而不是作为评论中的讨论

由于预读机制,会产生额外的读取。这将扫描叶级的父页面,以防需要发出异步IO将叶级页面带到缓冲区缓存中,以便在范围搜索到达它们时它们准备就绪


可以使用跟踪标志652禁用该机制(服务器范围内),并验证读取次数现在正是预期的10。

不是根页面。从我从其他查询中看到的情况来看,即使访问了它,它也不会影响逻辑IO计数;插入Accounts2(AccountID)中,从主..spt_值中选择不同的数字,其中数字介于1和21之间;设置统计IO;从Accounts2中选择*其中AccountID=5给出预期的2次逻辑读取。一个用于根页面,一个用于叶页面。即使它可能通过IAM找到了根页面,但读取不被计算。@Martin:嗯,看来你是对的。似乎有某种额外的检查只对非唯一索引执行。如果您从accountId=4的帐户中发出
选择前6个accountId(在您的原始设置中),将有
2个
读取,但如果您将其更改为
前7个
,将有
3个
读取(尽管
id=4
的所有7条记录都在一个页面中)。@Martin:在这里发布:,请登录并向上投票。得到答案!如果您尝试
dbcctraceon(652);从AccountID=4的科目中选择前7名AccountID;DBCC TRACEOFF(652)
您将看到两个逻辑读取。与我的原始查询类似,在启用此跟踪标志的情况下,您将获得预期的10次读取。Paul White解释说,基本上是另一个线程扫描上层页面,以防需要发出异步IO,这些读取显示为逻辑读取,但只有在实际需要从光盘读取时才显示为“预读”。
USE tempdb

SET NOCOUNT ON;

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

CREATE CLUSTERED INDEX ix ON Accounts(AccountID)


INSERT INTO Accounts(AccountID)
SELECT C
FROM (SELECT 4 UNION ALL SELECT 6 UNION ALL SELECT 7) Vals(C)
CROSS JOIN (SELECT TOP (7) 1 FROM master..spt_values) T(X)

DECLARE @AccountID INT

SET STATISTICS IO ON
SELECT @AccountID=AccountID FROM Accounts WHERE AccountID IN (4,6,7) 
SET STATISTICS IO OFF

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

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

DECLARE @index_info  TABLE
(PageFID  VARCHAR(10), 
  PagePID VARCHAR(10),   
  IAMFID   TINYINT, 
  IAMPID  INT, 
  ObjectID  INT,
  IndexID  TINYINT,
  PartitionNumber TINYINT,
  PartitionID BIGINT,
  iam_chain_type  VARCHAR(30),    
  PageType  TINYINT, 
  IndexLevel  TINYINT,
  NextPageFID  TINYINT,
  NextPagePID  INT,
  PrevPageFID  TINYINT,
  PrevPagePID INT, 
  PRIMARY KEY (PageFID, PagePID));
  
INSERT INTO @index_info 
    EXEC ('DBCC IND ( tempdb, Accounts, -1)'  ); 

DECLARE @DynSQL NVARCHAR(MAX) = 'DBCC TRACEON (3604);'
SELECT @DynSQL = @DynSQL + '
DBCC PAGE(tempdb, ' + PageFID + ', ' + PagePID + ', 3); '
FROM @index_info     
WHERE IndexLevel = 1

SET @DynSQL = @DynSQL + '
DBCC TRACEOFF(3604); '

CREATE TABLE #index_l1_info  
(FileId  INT, 
  PageId INT,   
  ROW   INT, 
  LEVEL  INT, 
  ChildFieldId  INT,
  ChildPageId INT,
  [AccountId (KEY)] INT,
  [UNIQUIFIER (KEY)] INT,
  KeyHashValue  VARCHAR(30));
  
INSERT INTO #index_l1_info  
EXEC(@DynSQL)


SELECT *
FROM #index_l1_info

DROP TABLE #index_l1_info
DROP TABLE Accounts