为什么使用预读(预取)进行更多(和不同数量的)逻辑读取?

use*_*199 8 sql-server database-internals

在我的 SQL Server 中创建 tpch 数据库后,我尝试了以下查询:

    set statistics io on
    DBCC DROPCLEANBUFFERS;        
    select top 100 * from dbo.lineitem order by l_partkey;
Run Code Online (Sandbox Code Playgroud)

表 lineitem 在 l_partkey 上有一个非聚集索引。我多次发出上述查询,发现每次逻辑读取都不同:

    Table 'lineitem'. Scan count 1, logical reads 1019, physical reads 4, read-ahead reads 1760, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'lineitem'. Scan count 1, logical reads 1007, physical reads 4, read-ahead reads 1720, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'lineitem'. Scan count 1, logical reads 1030, physical reads 4, read-ahead reads 1792, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Run Code Online (Sandbox Code Playgroud)

从这里的帖子:逻辑读取计数变化,我知道这可能是由预读行为引起的。

究竟为什么读会导致更多的逻辑读取?它如何改变 SQL Server 的行为?就像 SQL Server 可能会读取更多索引页一样,因为它无论如何都在缓存中?

无论如何,我禁用了预读并再次发出上述查询。现在它每次报告相同数量的逻辑读取。但是逻辑读取要小得多!!

    Table 'lineitem'. Scan count 1, logical reads 404, physical reads 160, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Run Code Online (Sandbox Code Playgroud)

所以我的问题是,为什么预读功能会导致更多不同的逻辑读取计数?

出于好奇,我尝试了另一个没有“order by”的查询:

    select top 100 * from dbo.lineitem
Run Code Online (Sandbox Code Playgroud)

这是没有提前阅读的结果:

    Table 'lineitem'. Scan count 1, logical reads 5, physical reads 3, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Run Code Online (Sandbox Code Playgroud)

这是预读的结果:

    Table 'lineitem'. Scan count 1, logical reads 15, physical reads 2, read-ahead reads 3416, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Run Code Online (Sandbox Code Playgroud)

带有预读的那个仍然有更多的逻辑读。所以为什么?

Pau*_*ite 9

ORDER BY l_partkey示例的查询计划几乎肯定会按顺序读取非聚集索引(使用预读),然后是键查找以检索未覆盖的列。

Lookup 上方的 Nested Loops Join 运算符可能会为 Lookup 使用额外的预取 ( WithOrderedPrefetch)。有关完整详细信息,请参阅我写的以下文章:

此外,正如我在回答链接的问答中提到的那样,预读读取的数量取决于时序和存储子系统特性。同样的注意事项适用于嵌套循环连接处的查找预取。

需要注意的是SQL Server问题预读该网页被索引扫描是必要的,但是这并不局限于通过TOP在查询规范。的TOP是一个查询处理器元件,而超前读由存储引擎控制。

这些活动是完全独立的:预读(和预取)为扫描(或查找)可能需要的页面发出异步I/O 。

根据 I/O 实际完成并使行可用于查询处理器(除其他外)的顺序,实际接触(逻辑读取)或物理读取的页数可能会有所不同。请特别注意,当 Lookup 延迟预取检查以查看 Lookup 所需的页面是否已经在内存中时,它也有助于逻辑读取。

因此,这一切都归结为重叠操作的详细计时:只要在 Top 迭代器上看到所需的行数 (100),查询处理器就会开始关闭查询执行管道。那时将发出或完成多少异步 I/O(预读或预取)基本上是不确定的。

您可以使用跟踪标志 8744禁用嵌套循环加入预取以进一步探索。这将从WithOrderedPrefetch嵌套循环连接中删除该属性。我通常OPTION (QUERYTRACEON 8744)在查询本身上使用。在任何情况下,您都需要确保没有重复使用具有预取的缓存计划。每次清除计划缓存或使用 强制查询重新编译OPTION (RECOMPILE)

逻辑读取是代表查询触及的缓存页面数量的简单度量。在启用预读(和/或预取)的情况下,可能会触及更多(和不同!)索引和数据页以发出预读或作为预取活动的一部分。