【问题标题】:Logical reads for seeks on a non unique clustered index在非唯一聚集索引上进行逻辑读取
【发布时间】:2011-08-19 12:46:33
【问题描述】:

对于表定义

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

包含 21 行(每个 AccountId 值 4,6,7 有 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

为什么?

我认为对于每次查找,它都会查找可能包含该值的第一页,然后(如有必要)continue along the linked list,直到它发现第一行不等于所查找的值。

但是,最多只能添加 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 个是什么原因?

完整的脚本重现

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

【问题讨论】:

    标签: sql-server performance indexing


    【解决方案1】:

    DBCC IND的输出中我看到,有1个根页(type = 10),1个关键页(type = 2)和4个叶页(type = 1),总共6页.

    所以每次扫描都以root -> key -> leaf -> … -> final leaf 进行,这为47 提供4 次读取,为6 提供5 次读取,总共4 + 4 + 5 = 13

    【讨论】:

    • Page Type 10the IAM page 不是根页面。根据我从其他查询中看到的情况,即使已访问它,它也不会影响逻辑 IO 计数。
    • 例如 CREATE TABLE Accounts2(AccountID INT PRIMARY KEY ,Filler CHAR(1000)); INSERT INTO Accounts2(AccountID) SELECT DISTINCT number FROM master..spt_values WHERE number between 1 and 21; SET STATISTICS IO ON; SELECT * FROM Accounts2 WHERE AccountID = 5 给出预期的 2 次逻辑读取。一个用于根页面,一个用于叶页面。即使它可能通过 IAM 找到了未计算读取的根页面。
    • @Martin:嗯,看来你是对的。似乎有某种额外的检查只对非唯一索引执行。如果您发出SELECT TOP 6 accountId FROM accounts WHERE accountId = 4(在您的原始设置中),则会有2 读取,但如果您将其更改为TOP 7,则会有3 读取(尽管id = 4 的所有7 条记录都在一页)。
    • @Martin:发在这里:connect.microsoft.com/SQLServer/feedback/details/667095/…,请登录并点赞。
    • 得到了答案!如果您尝试DBCC TRACEON (652);SELECT TOP 7 AccountID FROM Accounts WHERE AccountID = 4;DBCC TRACEOFF (652),您会看到 2 个逻辑读取。与我的原始查询类似,您会在此跟踪标志打开的情况下获得预期的 10 次读取。 Paul White 解释它in the comments here 基本上另一个线程扫描上层页面以防它需要发出异步 IO,这些读取显示为逻辑读取,但如果实际需要从磁盘读取,则仅显示为“预读读取”。
    【解决方案2】:

    只是以答案形式提供答案,而不是在 cmets 中讨论...

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

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

    【讨论】:

      猜你喜欢
      • 2014-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-28
      • 1970-01-01
      • 2010-10-23
      相关资源
      最近更新 更多