【问题标题】:How can LIKE '%...' seek on an index?LIKE '%...' 如何在索引上查找?
【发布时间】:2013-08-06 14:56:17
【问题描述】:

我希望这两个SELECTs 具有相同的执行计划和性能。由于LIKE 上有一个前导通配符,我希望进行索引扫描。当我运行它并查看计划时,第一个 SELECT 的行为符合预期(通过扫描)。但是第二个SELECT 计划显示了索引搜索,并且运行速度快了 20 倍。

代码:

-- Uses index scan, as expected:
SELECT 1
    FROM AccountAction
    WHERE AccountNumber LIKE '%441025586401'

-- Uses index seek somehow, and runs much faster:
declare @empty VARCHAR(30) = ''
SELECT 1
    FROM AccountAction
    WHERE AccountNumber LIKE '%441025586401' + @empty

问题:

当模式以通配符开头时,SQL Server 如何使用索引查找?

额外问题:

为什么连接一个空字符串会改变/改进执行计划?

详情:

  • Accounts.AccountNumber 上有一个非聚集索引
  • 还有其他索引,但查找和扫描都在这个索引上。
  • Accounts.AccountNumber 列是可为空的 varchar(30)
  • 服务器是 SQL Server 2012

表和索引定义:

CREATE TABLE [updatable].[AccountAction](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [AccountNumber] [varchar](30) NULL,
    [Utility] [varchar](9) NOT NULL,
    [SomeData1] [varchar](10) NOT NULL,
    [SomeData2] [varchar](200) NULL,
    [SomeData3] [money] NULL,
    --...
    [Created] [datetime] NULL,
 CONSTRAINT [PK_Account] PRIMARY KEY NONCLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]


CREATE NONCLUSTERED INDEX [IX_updatable_AccountAction_AccountNumber_UtilityCode_ActionTypeCd] ON [updatable].[AccountAction]
(
    [AccountNumber] ASC,
    [Utility] ASC
)
INCLUDE ([SomeData1], [SomeData2], [SomeData3]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]


CREATE CLUSTERED INDEX [CIX_Account] ON [updatable].[AccountAction]
(
    [Created] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

注意: 这是两个查询的实际执行计划。对象的名称与上面的代码略有不同,因为我试图让问题保持简单。

【问题讨论】:

  • 实际执行有区别还是仅估算计划有区别?
  • @GordonLinoff SQL Server 2012的版本号是11,2008 R2:10.5,2008:10等
  • 可能是参数嗅探,估计的计划可能没有意识到搜索实际上有多糟糕。如果在查询中使用OPTION (RECOMPILE)(或清除计划缓存)会发生什么?
  • 另外,请务必测量SET STATISTICS TIME ON;SET STATISTICS IO ON;,以便您查看seek或scan是否真的更好。 不要仅仅依靠 96% 和 4% 的数字来为您提供任何有关实际性能的指示 - 这些数字本身在很大程度上是没有意义的。
  • 我的直觉是,在你试图混淆的过程中,你遗漏了一些重要的细节,这些细节可以表明你为什么会被搜索。

标签: sql sql-server-2012 wildcard sql-execution-plan sql-like


【解决方案1】:

这些测试(数据库 AdventureWorks2008R2)显示了会发生什么:

SET NOCOUNT ON;
SET STATISTICS IO ON;

PRINT 'Test #1';
SELECT  p.BusinessEntityID, p.LastName
FROM    Person.Person p
WHERE   p.LastName LIKE '%be%';

PRINT 'Test #2';
DECLARE @Pattern NVARCHAR(50);
SET @Pattern=N'%be%';
SELECT  p.BusinessEntityID, p.LastName
FROM    Person.Person p
WHERE   p.LastName LIKE @Pattern;

SET STATISTICS IO OFF;
SET NOCOUNT OFF;

结果:

Test #1
Table 'Person'. Scan count 1, logical reads 106
Test #2
Table 'Person'. Scan count 1, logical reads 106

SET STATISTICS IO 的结果表明 LIO 相同。 但是执行计划完全不同:

在第一个测试中,SQL Server 显式使用Index Scan,但在第二个测试中,SQL Server 使用Index Seek,即Index Seek - range scan。在最后一种情况下,SQL Server 使用 Compute Scalar 运算符来生成这些值

[Expr1005] = Scalar Operator(LikeRangeStart([@Pattern])), 
[Expr1006] = Scalar Operator(LikeRangeEnd([@Pattern])), 
[Expr1007] = Scalar Operator(LikeRangeInfo([@Pattern]))

并且,Index Seek 运算符使用Seek Predicate(优化)作为range scanLastName > LikeRangeStart AND LastName < LikeRangeEnd)加上另一个未优化的PredicateLastName LIKE @pattern)。

LIKE '%...' 如何查找索引?

我的回答:它不是“真实的”Index Seek。这是一个Index Seek - range scan,在这种情况下,它的性能与Index Scan 相同。

另请参阅Index SeekIndex Scan 之间的区别(类似的辩论): So…is it a Seek or a Scan?.

编辑 1: OPTION(RECOMPILE) 的执行计划(请参阅 Aaron 的建议)还显示了 Index Scan(而不是 Index Seek):

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-26
    相关资源
    最近更新 更多