访问相同LOB数据时的逻辑读取不同

Pau*_*ite 26 performance sql-server database-internals blob

以下是三个读取相同数据的简单测试,但报告的逻辑读取却截然不同:

设置

以下脚本创建一个具有 100 个相同行的测试表,每个行都包含一个xml列,其中包含足够的数据以确保将其存储在行外。在我的测试数据库中,生成的xml的长度为每行 20,204 字节。

-- Conditional drop
IF OBJECT_ID(N'dbo.XMLTest', N'U') IS NOT NULL
    DROP TABLE dbo.XMLTest;
GO
-- Create test table
CREATE TABLE dbo.XMLTest
(
    ID integer IDENTITY PRIMARY KEY,
    X xml NULL
);
GO
-- Add 100 wide xml rows
DECLARE @X xml;

SET @X =
(
    SELECT TOP (100) *
    FROM  sys.columns AS C
    FOR XML 
        PATH ('row'),
        ROOT ('root'),
        TYPE
);

INSERT dbo.XMLTest
    (X)
SELECT TOP (100)
    @X
FROM  sys.columns AS C;

-- Flush dirty buffers
CHECKPOINT;
Run Code Online (Sandbox Code Playgroud)

测试

以下三个测试读取xml列:

  1. 一个简单的SELECT陈述
  2. xml分配给变量
  3. 使用SELECT INTO创建临时表
-- No row count messages or graphical plan
-- Show I/O statistics
SET NOCOUNT ON;
SET STATISTICS XML OFF;
SET STATISTICS IO ON;
GO
PRINT CHAR(10) + '=== Plain SELECT ===='

DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;

SELECT XT.X 
FROM dbo.XMLTest AS XT;
GO
PRINT CHAR(10) + '=== Assign to a variable ===='

DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;

DECLARE @X xml;

SELECT
    @X = XT.X
FROM dbo.XMLTest AS XT;
GO
PRINT CHAR(10) + '=== SELECT INTO ===='

IF OBJECT_ID(N'tempdb..#T', N'U') IS NOT NULL
    DROP TABLE #T;

DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;

SELECT 
    XT.X
INTO #T
FROM dbo.XMLTest AS XT
GO
SET STATISTICS IO OFF;
Run Code Online (Sandbox Code Playgroud)

结果

输出是:

=== 普通选择 ====
表“XMLTest”。扫描计数 1,逻辑读取 3,物理读取 1,预读读取 0,
    lob 逻辑读取 795,lob 物理读取 37,lob 预读读取 796。

=== 赋值给一个变量 ====
表“XMLTest”。扫描计数 1,逻辑读取 3,物理读取 1,预读读取 0,
    lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。

=== 选择进入 ====
表“XMLTest”。扫描计数 1,逻辑读取 3,物理读取 1,预读读取 0,
    lob 逻辑读取 300,lob 物理读取 37,lob 预读读取 400。

问题

  • 为什么 LOB 读取如此不同?
  • 每次测试中读取的数据肯定完全相同吗?

Pau*_*ite 27

并非所有读取都是平等的。SQL Server 知道访问 LOB 数据的开销很大,因此尽可能避免。每种情况下读取 LOB 数据的方式也存在详细差异:

概括

数字不同,因为:

  • select 以数据包大小的读取 LOB
  • 变量赋值测试根本不读取LOB
  • “选择进入”测试读取整个页面中的 LOB

细节

  1. 清楚的 SELECT

    选择方案

    聚集索引扫描不读取任何 LOB 数据。它只分配一个存储引擎 LOB句柄。在控制返回到计划的根之前,不会使用句柄。

    当前行的 LOB 内容以 TDS 数据包大小的块读取并流式传输到客户端。逻辑读取计算页面被触摸的次数,因此:

    报告的读取数等于执行的分块读取数,每次发生 LOB 页面转换时加一。

    例如:当进程触及与流的当前位置对应的页面时,在每个块的开始处计算逻辑读取。如果数据包小于数据库页面(通常情况下),则对同一页面计算多个逻辑读取。如果数据包太大以至于整个 LOB 可以放在一个块中,则报告的逻辑读取数将是 LOB 页数。

  2. 变量赋值

    可变计划

    聚集索引扫描像以前一样分配一个 LOB句柄。在计划的根部,LOB 句柄被复制到变量中。LOB 数据本身永远不会被访问(零 LOB 读取),因为永远不会读取变量。即使是,也只能通过最后分配的 LOB 句柄。

    没有 LOB 读取,因为从不访问 LOB 数据。

  3. SELECT INTO

    选择进入计划

    此计划使用批量行集提供程序将 LOB 数据从源表复制到新表。它在每次读取时处理一个完整的 LOB 页(无流或分块)。

    逻辑读取数对应于测试表中的 LOB 页数。