SQL Server中LIKE %search_string% 走索引查找(Index Seek)浅析
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SQL Server中LIKE %search_string% 走索引查找(Index Seek)浅析相关的知识,希望对你有一定的参考价值。
在SQL Server的SQL优化过程中,如果遇到WHERE条件中包含LIKE \'%search_string%\'是一件非常头痛的事情。这种情况下,一般要修改业务逻辑或改写SQL才能解决SQL执行计划走索引扫描或全表扫描的问题。最近在优化SQL语句的时候,遇到了一个很有意思的问题。某些使用LIKE \'%\' + @search_string + \'%\'(或者 LIKE @search_string)这样写法的SQL语句的执行计划居然走索引查找(Index Seek)。下面这篇文章来分析一下这个奇怪的现象。
首先,我们来看看WHERE查询条件中使用LIKE的几种情况,这些是我们对LIKE的一些常规认识:
1: LIKE \'condition%\'
执行计划会走索引查找(Index Seek or Clustered Index Seek)。
2: LIKE \'%condition\'
执行计划会走索引扫描(Index Scan or Clustered Index Scan)或全表扫描(Table Scan)
3: LIKE \'%condition%\'
执行计划会走索引扫描(Index Scan or Clustered Index Scan)或全表扫描(Table Scan)
4: LIKE \'condition1%condition%\';
执行计划会走索引查找(Index Seek)
下面我们以AdventureWorks2014示例数据库为测试环境(测试环境为SQL Server 2014 SP2),测试上面四种情况,如下所示:
其实复杂的情况下,LIKE \'search_string%\'也有走索引扫描(Index Scan)的情况,上面情况并不是唯一、绝对的。如下所示
在表Person.Person的 rowguid字段上创建有唯一索引AK_Person_rowguid
那么我们来看看上面所说的这个特殊案例(这里使用一个现成的案例,懒得构造案例了),如何让LIKE %search_string%走索引查找(Index Seek),这个技巧就是使用变量,如下SQL对比所示:
如下所示,表[dbo].[GEN_CUSTOMER]在字段CUSTOMER_CD有聚集索引。
可以看到CUSTOMER_CD LIKE \'%\' + @CUSTOMER_CD + \'%\'这样的SQL写法(或者CUSTOMER_CD LIKE @CUSTOMER_CD也可以), 执行计划就走聚集索引查找(Clustered Index Seek)了, 而条件中直接使用CUSTOMER_CD LIKE \'%00630%\' 反而走聚集索引扫描(Clustered Index Scan),另外可以看到实际执行的Cost开销比为4% VS 96% ,初一看,还真的以为第一个执行计划比第二个执行的代价要小很多。但是从IO开销,以及CPU time、elapsed time对比来看,两者几乎没有什么差异。在这个案例中,并不是走索引查找(Index Seek)就真的开销代价小很多。
考虑到这里数据量较小,我使用网上的一个脚本,在AdventureWorks2014数据库构造了一个10000000的大表,然后顺便做了一些测试对比
CREATE TABLE dbo.TestLIKESearches
(
ID1 INT
,ID2 INT
,AString VARCHAR(100)
,Value INT
,PRIMARY KEY (ID1, ID2)
);
WITH Tally (n) AS
(
SELECT TOP 10000000 ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM sys.all_columns a CROSS JOIN sys.all_columns b
)
INSERT INTO dbo.TestLIKESearches
(ID1, ID2, AString, Value)
SELECT 1+n/500, n%500
,CASE WHEN n%500 > 299 THEN
SUBSTRING(\'abcdefghijklmnopqrstuvwxyz\', 1+ABS(CHECKSUM(NEWID()))%26, 1) +
SUBSTRING(\'abcdefghijklmnopqrstuvwxyz\', 1+ABS(CHECKSUM(NEWID()))%26, 1) +
SUBSTRING(\'abcdefghijklmnopqrstuvwxyz\', 1+ABS(CHECKSUM(NEWID()))%26, 1) +
RIGHT(1000+n%1000, 3) +
SUBSTRING(\'abcdefghijklmnopqrstuvwxyz\', 1+ABS(CHECKSUM(NEWID()))%26, 1) +
SUBSTRING(\'abcdefghijklmnopqrstuvwxyz\', 1+ABS(CHECKSUM(NEWID()))%26, 1) +
SUBSTRING(\'abcdefghijklmnopqrstuvwxyz\', 1+ABS(CHECKSUM(NEWID()))%26, 1)
END
,1+ABS(CHECKSUM(NEWID()))%100
FROM Tally;
CREATE INDEX IX_TestLIKESearches_N1 ON dbo.TestLIKESearches(AString);
如下测试所示,在一个大表上面,LIKE @search_string这种SQL写法,IO开销确实要小一些,CPU Time也要小一些。个人多次测试都是这种结果。也就是说对于数据量较大的表,这种SQL写法性能确实要好一些。
现在回到最开始那个SQL语句,个人对执行计划有些疑惑,查看执行计划,你会看到优化器对CUSTOMER_CD LIKE \'%\' + @CUSTOMER_CD + \'%\' 进行了转换。如下截图或通过执行计划的XML,你会发现上面转换为使用三个内部函数LikeRangeStart, LikeRangeEnd, LikeRangeInfo.
<OutputList>
<ColumnReference Column="Expr1007" />
<ColumnReference Column="Expr1008" />
<ColumnReference Column="Expr1009" />
</OutputList>
<ComputeScalar>
<DefinedValues>
<DefinedValue>
<ColumnReference Column="Expr1007" />
<ScalarOperator ScalarString="LikeRangeStart((N\'%\'+[@CUSTOMER_CD])+N\'%\')">
<Identifier>
<ColumnReference Column="ConstExpr1004">
<ScalarOperator>
<Intrinsic FunctionName="LikeRangeStart">
<ScalarOperator>
<Arithmetic Operation="ADD">
<ScalarOperator>
<Arithmetic Operation="ADD">
<ScalarOperator>
<Const ConstValue="N\'%\'" />
</ScalarOperator>
<ScalarOperator>
<Identifier>
<ColumnReference Column="@CUSTOMER_CD" />
</Identifier>
</ScalarOperator>
如何在 SQL Server 查询中同时使用 LIKE 和 NOT LIKESql Server LIKE 查询中需要转义哪些字符[重复]