varchar(max)、nvarchar(max) 和 varbinary(max) 列会影响选择查询吗?

Sae*_*ati 5 performance sql-server datatypes t-sql

考虑这个表:

create table Books
(
    Id bigint not null primary key identity(1, 1),
    UniqueToken varchar(100) not null,
    [Text] nvarchar(max) not null
)
Run Code Online (Sandbox Code Playgroud)

假设这张表中有超过 100,000 本书。

现在我们有 10,000 本书数据要插入到这个表中,其中一些是重复的。所以我们需要先过滤重复,然后再插入新书。

检查重复项的一种方法是这样的:

select UniqueToken
from Books 
where UniqueToken in 
(
    'first unique token',
    'second unique token'
    -- 10,000 items here
)
Run Code Online (Sandbox Code Playgroud)

Text列的存在是否会影响此查询的性能?如果是这样,我们如何优化它?

PS 我有相同的结构,对于其他一些数据。而且它的表现并不好。一个朋友告诉我,我应该把我的桌子分成两张桌子,如下所示:

create table BookUniqueTokens 
(
    Id bigint not null primary key identity(1, 1),
    UniqueToken varchar(100)
)

create table Books
(
    Id bigint not null primary key,
    [Text] nvarchar(max)
)
Run Code Online (Sandbox Code Playgroud)

而且我必须只在第一个表上执行重复查找算法,然后将数据插入到它们中。他声称通过这种方式,性能会变得更好,因为表在物理上是分开的。他声称该[Text]列会影响对该列的任何select查询UniqueToken

Ran*_*gen 6

例子

考虑在IN10K 记录数据集的子句中使用 8 个过滤谓词的查询。

select UniqueToken
from Books 
where UniqueToken in 
(
    'Unique token 1',
    'Unique token 2',
    'Unique token 3',
    'Unique token 4',
    'Unique token 5',
    'Unique token 6',
    'Unique token 9999',
    'Unique token 5000'
    -- 10,000 items here
);
Run Code Online (Sandbox Code Playgroud)

使用聚集索引扫描,此测试表上没有其他索引

在此处输入图片说明

数据大小为216 字节

您还应该注意,即使有 8 条记录,OR过滤器也在堆积。

发生在该表上的读取:

在此处输入图片说明

归功于统计分析器

当您Text在查询的选择部分中包含该列时,实际数据大小会发生巨大变化:

select UniqueToken,Text
from Books 
where UniqueToken in 
(
    'Unique token 1',
    'Unique token 2',
    'Unique token 3',
    'Unique token 4',
    'Unique token 5',
    'Unique token 6',
    'Unique token 9999',
    'Unique token 5000'
    -- 10,000 items here
);
Run Code Online (Sandbox Code Playgroud)

再次,带有残差谓词的聚集索引扫描:

在此处输入图片说明

但是数据集为32KB

由于有近 1000 个 lob 逻辑读取:

在此处输入图片说明

现在,当我们创建有问题的两个表,并用相同的 10k 记录填充它们时

在没有Text. 请记住,使用Books表时我们有 99 次逻辑读取。

select UniqueToken
from BookUniqueTokens 
where UniqueToken in 
(
    'Unique token 1',
    'Unique token 2',
    'Unique token 3',
    'Unique token 4',
    'Unique token 5',
    'Unique token 6',
    'Unique token 9999',
    'Unique token 5000'
    -- 10,000 items here
)
Run Code Online (Sandbox Code Playgroud)

读数BookUniqueTokens 较低,为 67 而不是 99。

在此处输入图片说明

我们可以将其追溯到原始Books表中的页面和新表中没有Text.

Books表:

在此处输入图片说明

BookUniqueTokens

在此处输入图片说明

因此,所有页面 +(2 个开销页面?)都是从聚集索引中读取的。

为什么有差别,为什么差别不大?毕竟数据量差异很大(Lob数据<>没有Lob数据)

Books 数据空间

在此处输入图片说明

BooksWithText 数据空间

在此处输入图片说明

原因是ROW_OVERFLOW_DATA

当数据大于 8kb 时,数据将作为 ROW_OVERFLOW_DATA 存储在不同的页面上。

好吧,如果lob数据存储在不同的页面上,为什么这两个聚集索引的页面大小不一样?

由于将 24 字节指针添加到聚集索引以跟踪这些页面中的每一个。毕竟sql server需要知道哪里可以找到lob数据。

来源


回答您的问题

他声称 [Text] 列会影响 UniqueToken 列上的任何选择查询。

Text 列的存在是否会影响此查询的性能?如果是这样,我们如何优化它?

如果存储的数据实际上是Lob Data,则使用答案中提供的Query

由于 24 字节的指针,它确实带来了一些开销。

根据执行次数/分钟不是疯狂的高,我会说这是可以忽略不计的,即使有 100K 记录。

请记住,这种开销仅在包含以下内容的索引时发生 Text(例如聚集索引)时。

但是,如果使用聚集索引扫描,并且lob数据不超过8kb呢?

如果数据不超过 8kb,并且您没有索引UniqueToken,则开销可能会更大。即使没有选择Text列。

当 Text 只有 137 个字符长(所有记录)时,对 10k 条记录进行逻辑读取:

表'Books2'。扫描计数 1,逻辑读取 419

由于所有这些额外的数据都在聚集索引页上。

再次,一个索引UniqueToken(不包括Text列)将解决此问题。

正如@David Browne - Microsoft 所指出的,您还可以将数据存储在行外,以便在不选择此文本列时不会在聚集索引上添加此开销。

此外,如果您确实希望将文本存储在行外,则可以在不使用单独表的情况下强制执行该操作。只需使用 sp_tableoption 设置“行外大值类型”选项即可。docs.microsoft.com/en-us/sql/relational-databases

TL; 博士

根据给出的查询,UniqueToken不包含索引的索引TEXT应该可以解决您的问题。此外,我会使用临时表或表类型来进行过滤而不是IN语句。

编辑:

是的,UniqueToken 上有一个非聚集索引

您的示例查询未触及 Text列,根据查询,这应该是一个覆盖索引。

如果我们在之前使用的三个表上进行测试(UniqueToken+ Lob 数据、Solely UniqueTokenUniqueTokennvarchar(max) 列中的 + 137 个字符数据)

CREATE INDEX [IX_Books_UniqueToken] ON Books(UniqueToken);
CREATE INDEX [IX_BookUniqueTokens_UniqueToken]  ON BookUniqueTokens(UniqueToken);
CREATE INDEX [IX_Books2_UniqueToken] ON Books2(UniqueToken);
Run Code Online (Sandbox Code Playgroud)

这三个表的读取保持不变,因为使用了非聚集索引。

Table 'Books'. Scan count 8, logical reads 16, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table 'BookUniqueTokens'. Scan count 8, logical reads 16, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table 'Books2'. Scan count 8, logical reads 16, physical reads 5, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Run Code Online (Sandbox Code Playgroud)

额外细节

作者:@David Browne - 微软

此外,如果您确实希望将文本存储在行外,则可以在不使用单独表的情况下强制执行该操作。只需使用 sp_tableoption 设置“行外大值类型”选项即可。docs.microsoft.com/en-us/sql/relational-databases/

请记住,您必须重建索引才能使已填充的数据生效。

通过@Erik Darling

过滤 Lob 数据很糟糕。

当使用更大的数据类型时,您的内存授权可能会达到顶峰,从而影响性能。