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