在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>

                                        </Arithmetic>

                                      </ScalarOperator>

                                      <ScalarOperator>

                                        <Const ConstValue="N'%'" />

                                      </ScalarOperator>

                                    </Arithmetic>

                                  </ScalarOperator>

                                  <ScalarOperator>

                                    <Const ConstValue="" />

                                  </ScalarOperator>

                                </Intrinsic>

                              </ScalarOperator>

                            </ColumnReference>

                          </Identifier>

                        </ScalarOperator>

                      </DefinedValue>

                      <DefinedValue>

                        <ColumnReference Column="Expr1008" />

                        <ScalarOperator ScalarString="LikeRangeEnd((N'%'+[@CUSTOMER_CD])+N'%')">

                          <Identifier>

                            <ColumnReference Column="ConstExpr1005">

                              <ScalarOperator>

                                <Intrinsic FunctionName="LikeRangeEnd">

                                  <ScalarOperator>

                                    <Arithmetic Operation="ADD">

                                      <ScalarOperator>

                                        <Arithmetic Operation="ADD">

                                          <ScalarOperator>

                                            <Const ConstValue="N'%'" />

                                          </ScalarOperator>

                                          <ScalarOperator>

                                            <Identifier>

                                              <ColumnReference Column="@CUSTOMER_CD" />

                                            </Identifier>

                                          </ScalarOperator>

                                        </Arithmetic>

                                      </ScalarOperator>

                                      <ScalarOperator>

                                        <Const ConstValue="N'%'" />

                                      </ScalarOperator>

                                    </Arithmetic>

                                  </ScalarOperator>

                                  <ScalarOperator>

                                    <Const ConstValue="" />

                                  </ScalarOperator>

                                </Intrinsic>

                              </ScalarOperator>

                            </ColumnReference>

                          </Identifier>

                        </ScalarOperator>

                      </DefinedValue>

                      <DefinedValue>

                        <ColumnReference Column="Expr1009" />

                        <ScalarOperator ScalarString="LikeRangeInfo((N'%'+[@CUSTOMER_CD])+N'%')">

                          <Identifier>

                            <ColumnReference Column="ConstExpr1006">

                              <ScalarOperator>

                                <Intrinsic FunctionName="LikeRangeInfo">

                                  <ScalarOperator>

                                    <Arithmetic Operation="ADD">

                                      <ScalarOperator>

                                        <Arithmetic Operation="ADD">

                                          <ScalarOperator>

                                            <Const ConstValue="N'%'" />

                                          </ScalarOperator>

                                          <ScalarOperator>

                                            <Identifier>

                                              <ColumnReference Column="@CUSTOMER_CD" />

                                            </Identifier>

                                          </ScalarOperator>

                                        </Arithmetic>

                                      </ScalarOperator>

                                      <ScalarOperator>

                                        <Const ConstValue="N'%'" />

                                      </ScalarOperator>

                                    </Arithmetic>

                                  </ScalarOperator>

                                  <ScalarOperator>

                                    <Const ConstValue="" />

                                  </ScalarOperator>

                                </Intrinsic>

                              </ScalarOperator>

                            </ColumnReference>

                          </Identifier>

                        </ScalarOperator>

                      </DefinedValue>

                    </DefinedValues>

另外,你会发现Nested Loops & Compute Scalar 等步骤的Cost都为0.后面在“Dynamic Seeks and Hidden Implicit Conversions”这篇博客里面看到了一个新名词Dynamic Seeks。文字提到因为成本估算为0,所以,你看到的执行计划的Cost又是“不准确”的,具体描述如下:

The plan now contains an extra Constant Scan,  a Compute Scalar and a Nested Loops Join.  These operators are interesting because they have zero cost estimates: no CPU, no I/O, nothing.  That’s because they are purely architectural: a workaround for the fact that SQL Server cannot currently perform a dynamic seek within the Index Seek operator itself.  To avoid affecting plan choices, this extra machinery is costed at zero.

The Constant Scan produces a single in-memory row with no columns.  The Compute Scalar defines expressions to describe the covering seek range (using the runtime value of the @Like variable).  Finally, the Nested Loops Join drives the seek using the computed range information as correlated values.

The upper tooltip shows that the Compute Scalar uses three internal functions, LikeRangeStart, LikeRangeEnd, and LikeRangeInfo.  The first two functions describe the range as an open interval.  The third function returns a set of flags encoded in an integer, that are used internally to define certain seek properties for the Storage Engine.  The lower tooltip shows the seek on the open interval described by the result of LikeRangeStart and LikeRangeEnd, and the application of the residual predicate ‘LIKE @Like’.

不管你返回的记录有多少,执行计划Nested Loops & Compute Scalar 等步骤的Cost都为0,如下测试所示,返回1000条记录,它的成本估算依然为0 ,显然这样是不够精确的。深层次的原因就不太清楚了。执行计划Cost不可靠的案例很多。

SET STATISTICS IO ON;

 

SET STATISTICS TIME ON;

 

DECLARE @CUSTOMER_CD NVARCHAR(10);

 

SET @CUSTOMER_CD=N'%44%'

 

 

 

SELECT * FROM  [dbo].[GEN_CUSTOMER] WHERE CUSTOMER_CD LIKE @CUSTOMER_CD

另外,其实还一点没有搞清楚的时候在什么条件下出现Index Seek的情况。有些情况下,使用变量的方式,依然是索引扫描

不过我在测试过程,发现有一个原因是书签查找(Bookmark Lookup:键查找(Key Lookup)或RID查找 (RID Lookup))开销过大会导致索引扫描。如下测试对比所示:

CREATE NONCLUSTERED INDEX [IX_xriteWhite_N1] ON.[dbo].[xriteWhite] ([Item_NO]) INCLUDE ([Iden],[WI_CE],[CIE],[Operate_Time])

参考资料:

http://sqlblog.com/blogs/paul_white/archive/2012/01/18/dynamic-seeks-and-hidden-implicit-conversions.aspx

https://blogs.msdn.microsoft.com/varund/2009/11/30/index-usage-by-like-operator-query-tuning/

https://sqlperformance.com/2017/02/sql-indexes/seek-leading-wildcard-sql-server

https://stackoverflow.com/questions/1388059/sql-server-index-columns-used-in-like

SQL Server中LIKE %search_string% 走索引查找(Index Seek)浅析的更多相关文章

  1. SQL Server中的聚集索引(clustered index) 和 非聚集索引 (non-clustered index)

    本文转载自  http://blog.csdn.net/ak913/article/details/8026743 面试时经常问到的问题: 1. 什么是聚合索引(clustered index) / ...

  2. SQL SERVER中什么情况会导致索引查找变成索引扫描

    SQL Server 中什么情况会导致其执行计划从索引查找(Index Seek)变成索引扫描(Index Scan)呢? 下面从几个方面结合上下文具体场景做了下测试.总结.归纳. 1:隐式转换会导致 ...

  3. Sql Server中的表访问方式Table Scan, Index Scan, Index Seek

    1.oracle中的表访问方式 在oracle中有表访问方式的说法,访问表中的数据主要通过三种方式进行访问: 全表扫描(full table scan),直接访问数据页,查找满足条件的数据 通过row ...

  4. 转:Sql Server中的表访问方式Table Scan, Index Scan, Index Seek

    0.参考文献 Table Scan, Index Scan, Index Seek SQL SERVER – Index Seek vs. Index Scan – Diffefence and Us ...

  5. sql server中index的REBUILD和REORGANIZE的区别及工作方式

    sql server中index的REBUILD和REORGANIZE 转自:https://www.cnblogs.com/flysun0311/archive/2013/12/05/3459451 ...

  6. c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程

    c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...

  7. 第十七周翻译-SQL Server中事务日志管理的阶梯,级别5:以完全恢复模式管理日志

    SQL Server中事务日志管理的阶梯,级别5:以完全恢复模式管理日志 作者:Tony Davis,2012/01/27 翻译:赖慧芳 译文: 该系列   本文是Stairway系列的一部分:SQL ...

  8. SQL Server中SCAN 和SEEK的区别

    SQL Server中SCAN 和SEEK的区别 SQL SERVER使用扫描(scan)和查找(seek)这两种算法从数据表和索引中读取数据.这两种算法构成了查询的基础,几乎无处不在.Scan会扫描 ...

  9. SQL Server中的执行引擎入门

      简介 当查询优化器(Query Optimizer)将T-SQL语句解析后并从执行计划中选择最低消耗的执行计划后,具体的执行就会交由执行引擎(Execution Engine)来进行执行.本文旨在 ...

随机推荐

  1. MyBatis增、删、改、查

    1.config.xml文件的基本配置信息 2.选择数据源 3.mybatis约定 (1)parameterType和resultType 只能传一个参数,但是我们可以传一个数组或者集合,达到传多个参 ...

  2. PHP 编码规范

    这是给小组制定的php编码规范 该 PHP 编码规范基本上是同 PSR 规范的.有一部分的编码规范 PSR 中是建议,此编码规范会强制要求. 此编码规范 是以 PSR-1 / PSR-2 / PSR- ...

  3. 一步一步教你如何用Python做词云

    前言 在大数据时代,你竟然会在网上看到的词云,例如这样的. 看到之后你是什么感觉?想不想自己做一个? 如果你的答案是正确的,那就不要拖延了,现在我们就开始,做一个词云分析图,Python是一个当下很流 ...

  4. SpringCloud中使用Hystrix

    1.  引言 一般而言,一个服务都是部署了多台机器的,那么在这种情况下,当其中一个服务挂了以后Hystrix是怎么处理的呢? 为了验证这个问题,我们准备两个服务:user-api 和 app-gate ...

  5. 从零开始学习PYTHON3讲义(七)条件分支和哥德巴赫猜想

    <从零开始PYTHON3>第七讲 人生是由无数个选择组成,每个选择都有不同的限定条件.现在来说人生有点早是吧:)不过事实的确是这样的. 程序也充满着选择,满足不同的条件,则运行不同的运算. ...

  6. PyQt:个性化登录界面模仿QQ登录

    写在前面 写了一个登录界面的demo,类似QQ的,写的自己喜欢的样式,贴一下代码,先上效果,如下 陈述 PyQt5+Python3.5.2 login.py是里登录的主界面loginWnd类,Head ...

  7. SpringBoot上传文件到本服务器 目录与jar包同级

    前言 看标题好像很简单的样子,但是针对使用jar包发布SpringBoot项目就不一样了. 当你使用tomcat发布项目的时候,上传文件存放会变得非常简单,因为你可以随意操作项目路径下的资源.但是当你 ...

  8. 记录阿里云服务器mysql被黑

    前言 比上次服务器被黑还要恐怖的数据库被黑,再次强调,数据库不备份不做安全,你就可以准备跑路了. 这次记录一下整个被黑的过程,以及整个检查和处理的过程. 发现 上个月某一天,网站出现了无法登录的情况, ...

  9. 大战Java虚拟机【2】—— GC策略

    前言 前面我们已经知道了Java虚拟机所做的事情就是回收那些不用的垃圾,那些不用的对象.那么问题来了,我们如何知道一个对象我们不需要使用了呢?程序在使用的过程中会不断的创建对象,这些所创建的对象指不定 ...

  10. sql distinct详解以及优化

    一.distinct简介 distinct这个关键字来过滤掉多余的重复记录只保留一条,但往往只用 它来返回不重复记录的条数,而不是用它来返回不重记录的所有值.其原因是distinct只有用二重循环查询 ...