Ваш план выполнения
Рассматривая план запроса, мы видим, что один индекс затрагивается для обслуживания двух операций фильтрации.
Проще говоря, благодаря оператору TOP была поставлена цель строки. Гораздо больше информации и предпосылки на цели ряда можно найти здесь
Из того же источника:
Стратегия цели строки обычно означает предпочтение неблокирующих навигационных операций (например, объединение вложенных циклов, поиск по индексу и поиск) по сравнению с блокирующими операциями на основе множеств, такими как сортировка и хеширование. Это может быть полезно всякий раз, когда клиент может получить выгоду от быстрого запуска и постоянного потока строк (возможно, с более длительным общим временем выполнения - см. Пост Роба Фарли выше). Есть также более очевидные и традиционные способы использования, например, для представления результатов по странице за раз.
Вся таблица прощупывается в фильтрах с использованием левого полусоединения, у которого установлена цель строки, в надежде вернуть 5 строк как можно быстрее и эффективнее.
Этого не происходит, что приводит к множеству итераций по TVF .Fulltextmatch.
Воссоздание
Исходя из вашего плана , я смог несколько воссоздать вашу проблему:
CREATE TABLE dbo.Person(id int not null,lastname varchar(max));
CREATE UNIQUE INDEX ui_id ON dbo.Person(id)
CREATE FULLTEXT CATALOG ft AS DEFAULT;
CREATE FULLTEXT INDEX ON dbo.Person(lastname)
KEY INDEX ui_id
WITH STOPLIST = SYSTEM;
GO
INSERT INTO dbo.Person(id,lastname)
SELECT top(12000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
REPLICATE(CAST('A' as nvarchar(max)),80000)+ CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as varchar(10))
FROM master..spt_values spt1
CROSS APPLY master..spt_values spt2;
CREATE CLUSTERED INDEX cx_Id on dbo.Person(id);
Выполнение запроса
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 1 OR contains("lastName", '"B*"');
Результаты в план запроса, сопоставимый с вашим:
В приведенном выше примере B не существует в полнотекстовом индексе. В результате от параметра и данных зависит, насколько эффективным может быть план запроса.
Лучшее объяснение этому можно найти в Строковых Целях, Часть 2: Полу-соединения Пола Уайта.
... Другими словами, на каждой итерации применения мы можем прекратить смотреть на вход B, как только будет найдено первое совпадение, используя предикат объединения по нажатию вниз. Это именно то, для чего нужна цель строки: создание части плана, оптимизированной для быстрого возврата первых n совпадающих строк (где n = 1 здесь).
Например, изменив предикат, чтобы результаты были найдены намного раньше (в начале сканирования).
select top (5) *
from dbo.Person
where "id" = 124
or contains("lastName", '"A*"');
where "id" = 124
получает устранен за счет полнотекстового индекса предиката уже возвращаются 5 строк, удовлетворяющий TOP()
предикату.
Результаты показывают это также
id lastname
1 'AAA...'
2 'AAA...'
3 'AAA...'
4 'AAA...'
5 'AAA...'
И TVF казни:
Вставка некоторых новых строк
INSERT INTO dbo.Person
SELECT 12001, REPLICATE(CAST('B' as nvarchar(max)),80000);
INSERT INTO dbo.Person
SELECT 12002, REPLICATE(CAST('B' as nvarchar(max)),80000);
Выполнение запроса для поиска этих предыдущих вставленных строк
SELECT TOP (2) *
from dbo.Person
where "id" = 1
or contains("lastName", '"B*"');
Это снова приводит к слишком большому количеству итераций почти по всем строкам, чтобы вернуть последнее, но одно найденное значение.
id lastname
1 'AAA...'
12001 'BBB...'
Разрешающая
При удалении цели строки с помощью traceflag 4138
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 124
OR contains("lastName", '"B*"')
OPTION(QUERYTRACEON 4138 );
Оптимизатор использует шаблон соединения ближе к реализации a UNION
, в нашем случае это выгодно, так как он толкает предикаты вниз к их соответствующим поискам кластеризованного индекса, и не использует левый полусоединение оператора goals строки.
Другой способ написать это, не используя вышеупомянутый traceflag:
SELECT top (5) *
FROM
(
SELECT *
FROM dbo.Person
WHERE "id" = 1
UNION
SELECT *
FROM dbo.Person
WHERE contains("lastName", '"B*"')
) as A;
С полученным планом запроса:
где полнотекстовая функция применяется непосредственно
Как примечание, для op, исправление оптимизатора запросов traceflag 4199 решило его проблему. Он реализовал это, добавив OPTION(QUERYTRACEON(4199))
к запросу. Я не смог воспроизвести это поведение на моем конце. Это исправление содержит оптимизацию полусоединения:
Флаг трассировки: 4102 Функция: SQL 9 - Производительность запроса низкая, если план выполнения запроса содержит операторы полусоединения. Обычно операторы полусоединения генерируются, когда запрос содержит ключевое слово IN или ключевое слово EXISTS. Включите флаг 4102 и 4118, чтобы преодолеть это.
Источник
дополнительный
Во время оптимизации на основе затрат оптимизатор также может добавить катушку индекса в план выполнения, реализованный LogOp_Spool Index on fly Eager
(или физическим аналогом)
Это делает это с моим набором данных для, TOP(3)
но не дляTOP(2)
SELECT TOP (3) *
from dbo.Physician
where "id" = 1
or contains("lastName", '"B*"')
При первом выполнении нетерпеливая шпуля считывает и сохраняет весь ввод перед возвратом подмножества строк, запрошенного предикатом. Более поздние исполнения читают и возвращают то же или другое подмножество строк из рабочего стола, даже не выполняя дочерний процесс. снова узлы.
Источник
С предикатом поиска, примененным к этому индексу, требуется спул: