Медленный запрос при наличии «содержит» и «=» в предложении where

8

Следующий запрос занимает около 10 секунд для завершения таблицы с 12k записей

select top (5) *
from "Physician"
where "id" = 1 or contains("lastName", '"a*"')

Но если я изменю предложение where на

where "id" = 1

или

where contains("lastName", '"a*"')

Он вернется мгновенно.

Оба столбца проиндексированы, а столбец lastName также полнотекстовый.

CREATE TABLE Physician
(
   id         int identity    NOT NULL,
   firstName  nvarchar(100)   NOT NULL,
   lastName   nvarchar(100)   NOT NULL
);

ALTER TABLE Physician
  ADD CONSTRAINT Physician_PK
  PRIMARY KEY CLUSTERED (id);

CREATE NONCLUSTERED INDEX Physician_IX2
   ON Physician (firstName ASC);

CREATE NONCLUSTERED INDEX Physician_IX3
   ON Physician (lastName ASC);

CREATE FULLTEXT INDEX
    ON "Physician" ("firstName" LANGUAGE 0x0, "lastName" LANGUAGE 0x0)
    KEY INDEX "Physician_PK"
    ON "the_catalog"
    WITH stoplist = off;

Вот план выполнения

В чем может быть проблема?

Хуман Валибейги
источник
Я только что добавил определение таблицы
Hooman Valibeigi

Ответы:

11

Ваш план выполнения

Рассматривая план запроса, мы видим, что один индекс затрагивается для обслуживания двух операций фильтрации.

введите описание изображения здесь

Проще говоря, благодаря оператору 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*"')  

введите описание изображения здесь

При первом выполнении нетерпеливая шпуля считывает и сохраняет весь ввод перед возвратом подмножества строк, запрошенного предикатом. Более поздние исполнения читают и возвращают то же или другое подмножество строк из рабочего стола, даже не выполняя дочерний процесс. снова узлы.

Источник

С предикатом поиска, примененным к этому индексу, требуется спул:

введите описание изображения здесь

Рэнди Вертонген
источник
Не могли бы вы объяснить использование флагов трассировки? Из вашего кода немного неясно, что они делают
Джордж. Паласиос
1
@ George.Palacios Да, я все испортил: ^). Сделаем спасибо за отзыв!
Рэнди Вертонген
Ни один из предложенных вами флагов QUERYTRACEON (4138, 3604, 8607, 8612) не сработал, но, как оказалось, QUERYTRACEON 4199 решил проблему !!!!
Хуман Валибейги
Обратите внимание, что запрос выполняется медленно даже без оператора TOP
Хуман Валибейджи,
@HoomanValibeigi вы пробовали объединение в нижней части?
Рэнди Вертонген