Su plan de ejecucion
Al mirar el plan de consulta, podemos ver que se toca un índice para servir dos operaciones de filtro.
En pocas palabras, debido al operador TOP, se estableció un objetivo de fila. Puede encontrar mucha más información y requisitos previos sobre los objetivos de la fila aquí.
De esa misma fuente:
Una estrategia de objetivo de fila generalmente significa favorecer las operaciones de navegación sin bloqueo (por ejemplo, uniones de bucles anidados, búsquedas de índice y búsquedas) sobre las operaciones de bloqueo basadas en conjuntos como la clasificación y el hash. Esto puede ser útil siempre que el cliente pueda beneficiarse de un inicio rápido y un flujo constante de filas (con quizás un tiempo de ejecución general más largo; consulte la publicación de Rob Farley más arriba). También existen los usos más obvios y tradicionales, por ejemplo, al presentar resultados página por página.
Toda la tabla se sondea en los filtros con el uso de una semiunión izquierda que tiene un objetivo de fila establecido, con la esperanza de devolver las 5 filas lo más rápido y eficiente posible.
Esto no sucede, lo que resulta en muchas iteraciones sobre .Fulltextmatch TVF.
Recreando
De acuerdo con su plan , pude recrear un poco su problema:
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);
Ejecutando la consulta
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 1 OR contains("lastName", '"B*"');
Resultados en un plan de consulta comparable al suyo:
En el ejemplo anterior, B no existe en el índice de texto completo. Como resultado, depende del parámetro y los datos qué tan eficiente puede ser el plan de consulta.
Una mejor explicación de esto se puede encontrar en Row Goals, Parte 2: Semi Joins por Paul White
... En otras palabras, en cada iteración de una aplicación, podemos dejar de mirar la entrada B tan pronto como se encuentre la primera coincidencia, utilizando el predicado de unión hacia abajo. Este es exactamente el tipo de cosas para las que un objetivo de fila es bueno: generar parte de un plan optimizado para devolver las primeras n filas coincidentes rápidamente (donde n = 1 aquí).
Por ejemplo, cambiar el predicado para que los resultados se encuentren mucho antes (al comienzo de la exploración).
select top (5) *
from dbo.Person
where "id" = 124
or contains("lastName", '"A*"');
el where "id" = 124
es eliminado debido a la predicado índice de texto completo ya devolver 5 filas, satisfaciendo el TOP()
predicado.
Los resultados muestran esto también
id lastname
1 'AAA...'
2 'AAA...'
3 'AAA...'
4 'AAA...'
5 'AAA...'
Y las ejecuciones de TVF:
Insertar algunas filas nuevas
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);
Ejecutando la consulta para encontrar estas filas insertadas anteriormente
SELECT TOP (2) *
from dbo.Person
where "id" = 1
or contains("lastName", '"B*"');
De nuevo, esto genera demasiadas iteraciones en casi todas las filas para devolver el último valor encontrado.
id lastname
1 'AAA...'
12001 'BBB...'
Resolviendo
Al eliminar el objetivo de la fila utilizando traceflag 4138
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 124
OR contains("lastName", '"B*"')
OPTION(QUERYTRACEON 4138 );
El optimizador utiliza un patrón de unión más cercano a la implementación de a UNION
, en nuestro caso esto es favorable, ya que empuja los predicados hacia sus respectivas búsquedas de índice agrupado, y no usa el operador de semiunión izquierdo de la hilera.
Otra forma de escribir esto, sin usar el indicador de rastreo mencionado anteriormente:
SELECT top (5) *
FROM
(
SELECT *
FROM dbo.Person
WHERE "id" = 1
UNION
SELECT *
FROM dbo.Person
WHERE contains("lastName", '"B*"')
) as A;
Con el plan de consulta resultante:
donde la función de texto completo se aplica directamente
Como nota al margen, para op, el hotfix del optimizador de consultas traceflag 4199 resolvió su problema. Lo implementó agregando OPTION(QUERYTRACEON(4199))
a la consulta. No pude reproducir ese comportamiento de mi parte. Este hotfix contiene una optimización de semiunión:
Indicador de seguimiento: 4102 Función: SQL 9: el rendimiento de la consulta es lento si el plan de ejecución de la consulta contiene operadores de semiunión Normalmente, los operadores de semiunión se generan cuando la consulta contiene la palabra clave IN o la palabra clave EXISTS. Habilite las banderas 4102 y 4118 para superar esto.
Fuente
Extra
Durante la optimización basada en costos, el optimizador también podría agregar una cola de índice al plan de ejecución, implementado por LogOp_Spool Index on fly Eager
(o la contraparte física)
Lo hace con mi conjunto de datos para TOP(3)
pero no paraTOP(2)
SELECT TOP (3) *
from dbo.Physician
where "id" = 1
or contains("lastName", '"B*"')
En la primera ejecución, un spool ansioso lee y almacena la entrada completa antes de devolver el subconjunto de filas solicitado por Predicate. Las ejecuciones posteriores leen y devuelven el mismo o un subconjunto diferente de filas de la mesa de trabajo, sin tener que ejecutar el hijo nodos de nuevo.
Fuente
Con el predicado de búsqueda aplicado a este carrete ansioso de índice: