Esta es una decisión del optimizador basado en costos.
Los costos estimados utilizados en esta elección son incorrectos, ya que supone independencia estadística entre los valores en diferentes columnas.
Es similar al problema descrito en Row Goals Gone Rogue donde los números pares e impares están correlacionados negativamente.
Es fácil de reproducir.
CREATE TABLE dbo.animal(
id int IDENTITY(1,1) NOT NULL PRIMARY KEY,
colour varchar(50) NOT NULL,
species varchar(50) NOT NULL,
Filler char(10) NULL
);
/*Insert 20 million rows with 1% black and 1% swan but no black swans*/
WITH T
AS (SELECT TOP 20000000 ROW_NUMBER() OVER (ORDER BY @@SPID) AS RN
FROM master..spt_values v1,
master..spt_values v2,
master..spt_values v3)
INSERT INTO dbo.animal
(colour,
species)
SELECT CASE
WHEN RN % 100 = 1 THEN 'black'
ELSE CAST(RN % 100 AS VARCHAR(3))
END,
CASE
WHEN RN % 100 = 2 THEN 'swan'
ELSE CAST(RN % 100 AS VARCHAR(3))
END
FROM T
/*Create some indexes*/
CREATE NONCLUSTERED INDEX ix_species ON dbo.animal(species);
CREATE NONCLUSTERED INDEX ix_colour ON dbo.animal(colour);
Ahora intenta
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black'
AND species LIKE 'swan'
Esto le da el siguiente plan que tiene un costo 0.0563167
.
El plan puede realizar una combinación de fusión entre los resultados de los dos índices en la id
columna. ( Más detalles del algoritmo de combinación de combinación aquí ).
La combinación de combinación requiere que ambas entradas sean ordenadas por la clave de combinación.
Los índices no agrupados se ordenan por (species, id)
y (colour, id)
respectivamente (los índices no agrupados no únicos siempre tienen el localizador de fila agregado al final de la clave implícitamente si no se agrega explícitamente). La consulta sin comodines está realizando una búsqueda de igualdad en species = 'swan'
y colour ='black'
. Como cada búsqueda solo recupera un valor exacto de la columna inicial, las filas coincidentes se ordenarán por id
lo que este plan es posible.
Los operadores del plan de consulta se ejecutan de izquierda a derecha . Con el operador izquierdo solicitando filas de sus elementos secundarios, que a su vez solicitan filas de sus elementos secundarios (y así sucesivamente hasta llegar a los nodos hoja). El TOP
iterador dejará de solicitar más filas de su hijo una vez que se hayan recibido 10.
SQL Server tiene estadísticas sobre los índices que le indican que el 1% de las filas coinciden con cada predicado. Se supone que estas estadísticas son independientes (es decir, no están correlacionadas ni positiva ni negativamente) de modo que, en promedio, una vez que ha procesado 1,000 filas que coinciden con el primer predicado, encontrará 10 que coinciden con el segundo y puede salir. (el plan anterior en realidad muestra 987 en lugar de 1,000 pero lo suficientemente cerca).
De hecho, como los predicados están correlacionados negativamente, el plan real muestra que todas las 200,000 filas coincidentes debían procesarse a partir de cada índice, pero esto se mitiga en cierta medida porque las filas unidas cero también significa que realmente se necesitaban cero búsquedas.
Comparar con
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
Lo que da el siguiente plan que tiene un costo de 0.567943
La adición del comodín final ahora ha provocado un escaneo de índice. Sin embargo, el costo del plan sigue siendo bastante bajo para una exploración en una tabla de 20 millones de filas.
Agregar querytraceon 9130
muestra más información
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
OPTION (QUERYTRACEON 9130)
Se puede ver que SQL Server reconoce que solo necesitará escanear alrededor de 100,000 filas antes de encontrar 10 que coincidan con el predicado y TOP
pueda dejar de solicitar filas.
Nuevamente, esto tiene sentido con el supuesto de independencia como 10 * 100 * 100 = 100,000
Finalmente intentemos forzar un plan de intersección de índice
SELECT TOP 10 *
FROM animal WITH (INDEX(ix_species), INDEX(ix_colour))
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
Esto me da un plan paralelo con un costo estimado de 3.4625
La principal diferencia aquí es que el colour like 'black%'
predicado ahora puede coincidir con varios colores diferentes. Esto significa que ya no se garantiza que las filas de índice coincidentes para ese predicado se ordenen en orden de id
.
Por ejemplo, la búsqueda de índice like 'black%'
podría devolver las siguientes filas
+------------+----+
| Colour | id |
+------------+----+
| black | 12 |
| black | 20 |
| black | 23 |
| black | 25 |
| blackberry | 1 |
| blackberry | 50 |
+------------+----+
Dentro de cada color, los identificadores están ordenados, pero los identificadores en diferentes colores pueden no estarlo.
Como resultado, SQL Server ya no puede realizar una intersección de índice de combinación de combinación (sin agregar un operador de clasificación de bloqueo) y opta por realizar una combinación hash. Hash Join está bloqueando la entrada de compilación, por lo que ahora el costo refleja el hecho de que todas las filas coincidentes deberán procesarse a partir de la entrada de compilación en lugar de suponer que solo tendrá que escanear 1,000 como en el primer plan.
Sin embargo, la entrada de la sonda no está bloqueando y todavía estima incorrectamente que podrá detener el sondeo después de procesar 987 filas a partir de eso.
(Aquí encontrará más información sobre iteradores sin bloqueo frente a iteradores de bloqueo)
Dado el aumento de los costos de las filas adicionales estimadas y la unión de hash, el escaneo de índice agrupado parcial parece más barato.
En la práctica, por supuesto, el escaneo de índice agrupado "parcial" no es parcial en absoluto y necesita atravesar los 20 millones de filas en lugar de los 100 mil supuestos al comparar los planes.
Aumentar el valor de TOP
(o eliminarlo por completo) finalmente encuentra un punto de inflexión en el que el número de filas que estima que el escaneo de CI necesitará cubrir hace que ese plan parezca más costoso y vuelve al plan de intersección de índice. Para mí, el punto de corte entre los dos planes es TOP (89)
vs TOP (90)
.
Para usted, puede diferir, ya que depende de qué tan ancho sea el índice agrupado.
Eliminar TOP
y forzar el escaneo CI
SELECT *
FROM animal WITH (INDEX = 1)
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
Cuesta en 88.0586
mi máquina para mi tabla de ejemplo.
Si SQL Server supiera que el zoológico no tenía cisnes negros y que necesitaría hacer un análisis completo en lugar de solo leer 100,000 filas, este plan no se elegiría.
He intentado varias columnas estadísticas sobre animal(species,colour)
y animal(colour,species)
y se filtra en las estadísticas animal (colour) where species = 'swan'
pero ninguna de ellas ayuda convencerlo de que no existen cisnes negros y la TOP 10
exploración tendrá que procesar más de 100.000 filas.
Esto se debe a la "suposición de inclusión" donde SQL Server esencialmente asume que si está buscando algo, probablemente exista.
En 2008+ hay una marca de seguimiento documentada 4138 que desactiva los objetivos de la fila. El efecto de esto es que el plan tiene un costo sin la suposición de que TOP
permitirá a los operadores secundarios terminar antes de tiempo sin leer todas las filas coincidentes. Con esta marca de seguimiento en su lugar, naturalmente obtengo el plan de intersección de índice más óptimo.
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
OPTION (QUERYTRACEON 4138)
Este plan ahora cuesta correctamente leer las 200 mil filas completas en ambas búsquedas de índice, pero sobrepasa los costos de las búsquedas clave (se estima que 2 mil frente a 0 real TOP 10
. . Aún así, el plan tiene un costo significativamente más barato que el análisis de CI completo, por lo que se selecciona.
Por supuesto, este plan podría no ser óptimo para combinaciones que son comunes. Como los cisnes blancos.
Un índice compuesto en animal (colour, species)
o idealmente animal (species, colour)
permitiría que la consulta sea mucho más eficiente para ambos escenarios.
Para hacer un uso más eficiente del índice compuesto LIKE 'swan'
, también debería cambiarse a = 'swan'
.
La siguiente tabla muestra los predicados de búsqueda y los predicados residuales que se muestran en los planes de ejecución para las cuatro permutaciones.
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
| WHERE clause | Index | Seek Predicate | Residual Predicate |
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
| colour LIKE 'black%' AND species LIKE 'swan' | ix_colour_species | colour >= 'black' AND colour < 'blacL' | colour like 'black%' AND species like 'swan' |
| colour LIKE 'black%' AND species LIKE 'swan' | ix_species_colour | species >= 'swan' AND species <= 'swan' | colour like 'black%' AND species like 'swan' |
| colour LIKE 'black%' AND species = 'swan' | ix_colour_species | (colour,species) >= ('black', 'swan')) AND colour < 'blacL' | colour LIKE 'black%' AND species = 'swan' |
| colour LIKE 'black%' AND species = 'swan' | ix_species_colour | species = 'swan' AND (colour >= 'black' and colour < 'blacL') | colour like 'black%' |
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
TOP
valor en una variable significa que asumirá enTOP 100
lugar deTOP 10
. Esto puede o no ayudar dependiendo del punto de inflexión entre los dos planes.