Как LIKE '%' может искать по индексу?

Я ожидаю, что эти два SELECT будут иметь одинаковый план выполнения и производительность. Поскольку в 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]

ПРИМЕЧАНИЕ. Вот фактический план выполнения двух запросов. Имена объектов немного отличаются от приведенного выше кода, потому что я пытался упростить вопрос.

Два плана выполнения.


person blackboxlogic    schedule 05.08.2013    source источник
comment
есть ли разница в фактическом исполнении или только в оценочных планах?   -  person Roman Pekar    schedule 05.08.2013
comment
@GordonLinoff Номер версии SQL Server 2012 — 11, 2008 R2: 10.5, 2008: 10 и т. д.   -  person swasheck    schedule 06.08.2013
comment
Я не знаю, насколько это может быть важно, но запросы, которые вы на самом деле выполняли, были LIKE '%441025586401%' с подстановочным знаком в начале и в конце.   -  person Lamak    schedule 06.08.2013
comment
Это может быть прослушивание параметров, и предполагаемый план может не осознавать, насколько плохим может быть этот поиск. Что произойдет, если вы укажете OPTION (RECOMPILE) в запросе (или очистите кэш планов)?   -  person Aaron Bertrand    schedule 06.08.2013
comment
@RomanPekar Это реальные планы выполнения.   -  person blackboxlogic    schedule 06.08.2013
comment
Вы не опубликовали код индекса, который фактически используется. Похоже, он ищет индекс в AccountAction (индекс с именем IX_updateable_Accou...). Это не один из индексов, которые вы опубликовали, и его нет в таблице.   -  person swasheck    schedule 06.08.2013
comment
@Lamak Я убрал конечные подстановочные знаки для простоты, но результаты не меняются из-за завершающего подстановочного знака.   -  person blackboxlogic    schedule 06.08.2013
comment
Также обязательно измерьте SET STATISTICS TIME ON; и SET STATISTICS IO ON;, чтобы увидеть, что на самом деле лучше: поиск или сканирование. Не полагайтесь только на цифры 96 % и 4 %, чтобы получить какое-либо представление о фактической производительности — эти цифры сами по себе в значительной степени бессмысленны.   -  person Aaron Bertrand    schedule 06.08.2013
comment
Также используется таблица AccountAction или Account? Помогите нам сопоставить DDL, который вы включили в планы. Не могли бы вы опубликовать фактические планы выполнения где-нибудь?   -  person Aaron Bertrand    schedule 06.08.2013
comment
Я пробовал эти запросы на своем SQL 2008 R2. Если я вижу план выполнения, он показывает, что второй ДОЛЖЕН работать быстрее, например, от 5% до 95%. Фактическое выполнение намного ближе, но с переменной @empty еще быстрее...   -  person Roman Pekar    schedule 06.08.2013
comment
Я подозреваю, что в ваших попытках запутать вы упустили некоторые важные детали, которые могли бы указать, почему вы получаете поиск.   -  person swasheck    schedule 06.08.2013
comment
@swasheck, нет, это так просто - я только что создал таблицу, заполнил ее строками, и эти два запроса показывают разные планы   -  person Roman Pekar    schedule 06.08.2013
comment
@swasheck . . . Спасибо за информацию о версии. Вот почему я спросил.   -  person Gordon Linoff    schedule 06.08.2013
comment
@GordonLinoff не беспокойся. ТМИК.   -  person swasheck    schedule 06.08.2013
comment
@RomanPekar вы выполнили оба запроса с OPTION (RECOMPILE)?   -  person swasheck    schedule 06.08.2013
comment
@swasheck это не имеет значения. На самом деле, теперь я вижу, что с пустой переменной работает дольше, я думаю, что ошибся.   -  person Roman Pekar    schedule 06.08.2013
comment
@pants Мне было интересно, опубликуете ли вы фактические запросы. Возможно, даже разместите ссылку на SQLFiddle с вашей структурой и некоторыми примерами данных.   -  person swasheck    schedule 06.08.2013
comment
При включенных параметрах OPTION(RECOMPILE) swasheck и STATISTICS TIME и STATISTICS IO AaronBertrand он переключается на использование сканирования индекса для обоих запросов. Их «затраты» составляют 50% партии, а их процессорное время очень близко.   -  person blackboxlogic    schedule 06.08.2013
comment
штаны: на самом деле @AaronBertrand порекомендовал это первым :D. Так что посмотрите на параметр обнюхивания вещь.   -  person swasheck    schedule 06.08.2013


Ответы (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 scan (LastName > LikeRangeStart AND LastName < LikeRangeEnd) плюс еще один неоптимизированный Predicate (LastName LIKE @pattern).

Как LIKE '%...' может искать по индексу?

Мой ответ: это не "настоящий" Index Seek. Это Index Seek - range scan, который в данном случае имеет те же характеристики, что и Index Scan.

Обратите также внимание на разницу между Index Seek и Index Scan (похожие дебаты): Так… это поиск или сканирование?.

Редактировать 1: План выполнения для OPTION(RECOMPILE) (см. рекомендацию Аарона, пожалуйста) также показывает Index Scan (вместо Index Seek): введите здесь описание изображения

person Bogdan Sahlean    schedule 05.08.2013