SEa*_*986 3 performance sql-server execution-plan type-conversion query-performance
我有一个疑问:
SELECT Id,
ColumnA,
ColumnB
FROM MyTable
WHERE ColumnA = @varA OR
ColumnB = @varB
Run Code Online (Sandbox Code Playgroud)
该表定义为
CREATE TABLE MyTable
(
Id INT IDENTITY(-2147483648,1) PRIMARY KEY,
ColumnA VARCHAR(22)
ColumnB VARCAHR(22)
)
Run Code Online (Sandbox Code Playgroud)
并且表上有一个非聚集索引
CREATE INDEX IX_MyIndex ON MyTable
(
ColumnA
)
Run Code Online (Sandbox Code Playgroud)
当我使用以下参数运行查询时:
DECLARE @varA nvarchar(4000) = ''
DECLARE @varB nvarchar(8) = '10140730'
Run Code Online (Sandbox Code Playgroud)
执行计划显示索引搜索IX_MyIndex,但它显示读取的行数为 1700 万行,但实际行数为 0(MyTable.ColumnA 中有 0 行,值为 '')如果我转动,SET STATISTICS IO ON我可以看到完整的表正在阅读
这是有道理的:“这是一个“糟糕的”索引搜索部分中的这篇文章
但是,当我使用参数运行相同的查询时:
DECLARE @varA nvarchar(8) = 'a'
DECLARE @varB nvarchar(8) = '10140730'
Run Code Online (Sandbox Code Playgroud)
搜索运算符没有“读取的行数”属性(MyTable.ColumnA 中有 0 行,值为 'a')并SET STATISTICS IO报告单图逻辑读取
顺便说一句,该计划有一个隐式转换警告,当我像这样更改查询时,问题就消失了:
SELECT Id,
ColumnA,
ColumnB
FROM MyTable
WHERE ColumnA = CONVERT(VARCHAR(22),@varA) OR
ColumnB = CONVERT(VARCHAR(22),@varB)
Run Code Online (Sandbox Code Playgroud)
或将基础列更改为 NVARCHAR
但是,我很好奇为什么索引查找的行为与两个不同的值@varA不同,即使它们都返回表中相同数量的记录 (0)
当列的数据类型与变量的数据类型不匹配时,SQL Server 无法直接使用 B 树索引的查找能力来定位正确的值范围。
当数据类型优先规则意味着必须将列数据转换为变量的数据类型时,这意味着扫描整个表或索引,转换每个值并将其作为剩余谓词针对变量进行测试。
这显然不是理想的,但很常见(不幸的是)以至于 SQL Server 有一个内置的方法来在这些情况下实现索引查找。它获取提供的值并计算它映射到的值的范围,考虑类型转换和排序规则。
此功能称为动态搜索,计算映射范围的内部方法称为GetRangeThroughConvert。
例如,当nvarchar变量包含“a”时,数据类型值的映射范围varchar可能是“a”到“B”(确切范围取决于排序规则)。这意味着 SQL Server 可以查找varchar'a' 和 'B' 之间的索引,只测试与 'a' (as nvarchar) 作为剩余谓词的匹配是否相等。
当提供的值为空字符串时,计算范围是无限的,因此整个索引被有效地扫描。
例如:
DROP TABLE IF EXISTS dbo.MyTable;
GO
CREATE TABLE dbo.MyTable
(
ColumnA varchar(22) COLLATE Latin1_General_CI_AS NOT NULL
);
GO
INSERT dbo.MyTable
WITH (TABLOCKX)
(ColumnA)
SELECT TOP (1000)
REPLICATE(CHAR(65 + ROW_NUMBER() OVER (ORDER BY @@SPID) % 26), 22)
FROM master.sys.all_columns AS AC1
CROSS JOIN master.sys.all_columns AS AC2;
GO
CREATE INDEX IX_MyIndex_A ON dbo.MyTable(ColumnA);
Run Code Online (Sandbox Code Playgroud)
以下查询使用动态搜索,范围为“a”到“B”,剩余谓词为CONVERT_IMPLICIT(nvarchar(22),[dbo].[MyTable].[ColumnA],0)=[@varA]:
DECLARE @varA nvarchar(22) = N'a';
SELECT MT.ColumnA
FROM dbo.MyTable AS MT
WHERE MT.ColumnA = @varA;
Run Code Online (Sandbox Code Playgroud)
的执行计划示出了动态寻求与由寻求合格38行的形状,但所有最终由残余拒绝:
38 行是查询计数的行:
SELECT COUNT_BIG(*)
FROM dbo.MyTable AS MT
WHERE MT.ColumnA > 'a'
AND MT.ColumnA < 'B';
Run Code Online (Sandbox Code Playgroud)
当变量包含空字符串时,计算的范围是无界的,因此搜索有效地扫描整个索引:
DECLARE @varA nvarchar(22) = N''; -- empty string
SELECT MT.ColumnA
FROM dbo.MyTable AS MT
WHERE MT.ColumnA = @varA;
Run Code Online (Sandbox Code Playgroud)
的执行计划显示了所有1000行从索引被读取(但同样,由残留丢弃):
空字符串是GetRangeThroughConvert无法产生有用范围的特殊情况。单个空格字符确实会产生狭窄的搜索范围(plan)。
无论如何,消息是要仔细注意数据类型。