以下是读取相同数据但报告非常不同的逻辑读取的三个简单测试:
设置
以下脚本创建了一个包含 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;
测试
以下三个测试读取xml列:
- 一个简单的
SELECT
声明 - 将xml分配给变量
- 用于
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;
结果
输出是:
=== 普通选择 ==== 表“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 读取如此不同?
- 肯定在每次测试中都读取了完全相同的数据吗?
并非所有读取都是平等的。SQL Server 知道访问 LOB 数据的成本很高,并尽可能避免访问。在每种情况下读取 LOB 数据的方式也存在详细差异:
概括
数字不同是因为:
细节
清楚的
SELECT
聚簇索引扫描不读取任何 LOB 数据。它只分配一个存储引擎 LOB句柄。在控制返回到计划的根之前,句柄不会被使用。
当前行的 LOB 内容以 TDS 数据包大小的块读取并流式传输到客户端。逻辑读取计数页面被触摸的次数,因此:
报告的读取数等于执行的分块读取数加上每次发生 LOB 页面转换时的读取数。
例如:当进程接触与流的当前位置相对应的页面时,在每个块的开头计算逻辑读取。如果数据包小于数据库页面(通常情况),则对同一页面计算多个逻辑读取。如果数据包太大以至于整个 LOB 可以放在一个块中,则报告的逻辑读取数将是 LOB 页数。
变量赋值
聚簇索引扫描像以前一样分配一个 LOB句柄。在计划的根部,LOB 句柄被复制到变量。LOB 数据本身永远不会被访问(零 LOB 读取),因为变量永远不会被读取。即使是,也只能通过最后分配的 LOB 句柄。
没有 LOB 读取,因为从不访问 LOB 数据。
SELECT INTO
该计划使用批量行集提供程序将 LOB 数据从源表复制到新表。它在每次读取时处理一个完整的 LOB 页面(无流或分块)。
逻辑读取的数量对应于测试表中的 LOB 页数。