Consulta lenta cuando tiene 'contiene' y '=' juntos en la cláusula where

8

La siguiente consulta tarda unos 10 segundos en finalizar en una tabla con 12k registros

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

Pero si cambio la cláusula where a cualquiera

where "id" = 1

o

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

Volverá al instante.

Ambas columnas están indexadas y la columna lastName también está indexada en texto completo.

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;

Aquí está el plan de ejecución

¿Cual podría ser el problema?

Hooman Valibeigi
fuente
Acabo de agregar la definición de la tabla
Hooman Valibeigi el

Respuestas:

11

Su plan de ejecucion

Al mirar el plan de consulta, podemos ver que se toca un índice para servir dos operaciones de filtro.

ingrese la descripción de la imagen aquí

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.

ingrese la descripción de la imagen aquí


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:

ingrese la descripción de la imagen aquí

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*"');

ingrese la descripción de la imagen aquí

el where "id" = 124es 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:

ingrese la descripción de la imagen aquí

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.

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

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.

ingrese la descripción de la imagen aquí

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:

ingrese la descripción de la imagen aquí

donde la función de texto completo se aplica directamente

ingrese la descripción de la imagen aquí

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*"')  

ingrese la descripción de la imagen aquí

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:

ingrese la descripción de la imagen aquí

Randi Vertongen
fuente
¿Podría explicar el uso de las banderas de seguimiento? No está claro por su código lo que están haciendo
George.Palacios
1
@ George.Palacios Sí, hice un desastre de todo: ^). Haré gracias por los comentarios!
Randi Vertongen
Ninguno de los indicadores de QUERYTRACEON sugeridos (4138, 3604, 8607, 8612) funcionó, pero QUERYTRACEON 4199 soluciona el problema.
Hooman Valibeigi
Tenga en cuenta que la consulta es lenta incluso sin el operador TOP
Hooman Valibeigi
@HoomanValibeigi, ¿probaste la solución sindical en la parte inferior?
Randi Vertongen