Elimine el operador de búsqueda de clave (en clúster) que ralentiza el rendimiento


16

¿Cómo puedo eliminar un operador de búsqueda de claves (agrupado) en mi plan de ejecución?

La tabla tblQuotesya tiene un índice agrupado ( QuoteIDactivado) y 27 índices no agrupados, por lo que estoy tratando de no crear más.

Puse la columna de índice agrupado QuoteIDen mi consulta, esperando que ayude, pero desafortunadamente sigue siendo la misma.

Plan de ejecución aquí .

O verlo:

ingrese la descripción de la imagen aquí

Esto es lo que dice el operador de búsqueda clave:

ingrese la descripción de la imagen aquí

Consulta:

declare
        @EffDateFrom datetime ='2017-02-01',
        @EffDateTo   datetime ='2017-08-28'

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

IF OBJECT_ID('tempdb..#Data') IS NOT NULL
    DROP TABLE #Data 
CREATE TABLE #Data
(
    QuoteID int NOT NULL,   --clustered index

    [EffectiveDate] [datetime] NULL, --not indexed
    [Submitted] [int] NULL,
    [Quoted] [int] NULL,
    [Bound] [int] NULL,
    [Exonerated] [int] NULL,
    [ProducerLocationId] [int] NULL,
    [ProducerName] [varchar](300) NULL,
    [BusinessType] [varchar](50) NULL,
    [DisplayStatus] [varchar](50) NULL,
    [Agent] [varchar] (50) NULL,
    [ProducerContactGuid] uniqueidentifier NULL
)
INSERT INTO #Data
    SELECT 
        tblQuotes.QuoteID,

          tblQuotes.EffectiveDate,
          CASE WHEN lstQuoteStatus.QuoteStatusID >= 1   THEN 1 ELSE 0 END AS Submitted,
          CASE WHEN lstQuoteStatus.QuoteStatusID = 2 or lstQuoteStatus.QuoteStatusID = 3 or lstQuoteStatus.QuoteStatusID = 202 THEN 1 ELSE 0 END AS Quoted,
          CASE WHEN lstQuoteStatus.Bound = 1 THEN 1 ELSE 0 END AS Bound,
          CASE WHEN lstQuoteStatus.QuoteStatusID = 3 THEN 1 ELSE 0 END AS Exonareted,
          tblQuotes.ProducerLocationID,
          P.Name + ' / '+ P.City as [ProducerName], 
        CASE WHEN tblQuotes.PolicyTypeID = 1 THEN 'New Business' 
             WHEN tblQuotes.PolicyTypeID = 3 THEN 'Rewrite'
             END AS BusinessType,
        tblQuotes.DisplayStatus,
        tblProducerContacts.FName +' '+ tblProducerContacts.LName as Agent,
        tblProducerContacts.ProducerContactGUID
FROM    tblQuotes 
            INNER JOIN lstQuoteStatus 
                on tblQuotes.QuoteStatusID=lstQuoteStatus.QuoteStatusID
            INNER JOIN tblProducerLocations P 
                On P.ProducerLocationID=tblQuotes.ProducerLocationID
            INNER JOIN tblProducerContacts 
                ON dbo.tblQuotes.ProducerContactGuid = tblProducerContacts.ProducerContactGUID

WHERE   DATEDIFF(D,@EffDateFrom,tblQuotes.EffectiveDate)>=0 AND DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <=0
        AND dbo.tblQuotes.LineGUID = '6E00868B-FFC3-4CA0-876F-CC258F1ED22D'--Surety
        AND tblQuotes.OriginalQuoteGUID is null

select * from #Data

Plan de ejecución:

ingrese la descripción de la imagen aquí


Las filas estimadas vs reales muestran una diferencia notable. Quizás SQL elige un mal plan porque no tiene los datos para hacer una buena estimación. ¿Con qué frecuencia actualiza sus estadísticas?
RDFozz

Respuestas:


23

Las búsquedas clave de varios tipos se producen cuando el procesador de consultas necesita obtener valores de columnas que no están almacenadas en el índice utilizado para ubicar las filas necesarias para que la consulta devuelva resultados.

Tomemos, por ejemplo, el siguiente código, donde estamos creando una tabla con un índice único:

USE tempdb;

IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO

CREATE TABLE dbo.Table1
(
    Table1ID int NOT NULL IDENTITY(1,1)
    , Table1Data nvarchar(30) NOT NULL
);

CREATE INDEX IX_Table1
ON dbo.Table1 (Table1ID);
GO

Insertaremos 1,000,000 de filas en la tabla para tener algunos datos con los que trabajar:

INSERT INTO dbo.Table1 (Table1Data)
SELECT TOP(1000000) LEFT(c.name, 30)
FROM sys.columns c
    CROSS JOIN sys.columns c1
    CROSS JOIN sys.columns c2;
GO

Ahora, consultaremos los datos con la opción de mostrar el plan de ejecución "real":

SELECT *
FROM dbo.Table1
WHERE Table1ID = 500000;

El plan de consulta muestra:

ingrese la descripción de la imagen aquí

La consulta mira el IX_Table1índice para encontrar la fila Table1ID = 5000000ya que mirar ese índice es mucho más rápido que escanear toda la tabla en busca de ese valor. Sin embargo, para satisfacer los resultados de la consulta, el procesador de consultas también debe encontrar el valor para las otras columnas de la tabla; aquí es donde entra la "Búsqueda de RID". Busca en la tabla el ID de fila (el RID en Búsqueda de RID) asociado con la fila que contiene el Table1IDvalor de 500000, obteniendo los valores de la Table1Datacolumna. Si coloca el mouse sobre el nodo "RID Lookup" en el plan, verá esto:

ingrese la descripción de la imagen aquí

La "Lista de resultados" contiene las columnas devueltas por la búsqueda de RID.

Una tabla con un índice agrupado y un índice no agrupado es un ejemplo interesante. La siguiente tabla tiene tres columnas; ID que es la clave de agrupación, Datque está indexado por un índice no agrupado IX_Table, y una tercera columna, Oth.

USE tempdb;

IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO

CREATE TABLE dbo.Table1
(
    ID int NOT NULL IDENTITY(1,1) 
        PRIMARY KEY CLUSTERED
    , Dat nvarchar(30) NOT NULL
    , Oth nvarchar(3) NOT NULL
);

CREATE INDEX IX_Table1
ON dbo.Table1 (Dat);
GO

INSERT INTO dbo.Table1 (Dat, Oth)
SELECT TOP(1000000) CRYPT_GEN_RANDOM(30), CRYPT_GEN_RANDOM(3)
FROM sys.columns c
    CROSS JOIN sys.columns c1
    CROSS JOIN sys.columns c2;
GO

Tome esta consulta de ejemplo:

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';

Le pedimos a SQL Server que devuelva cada columna de la tabla donde la Datcolumna contiene la palabra Test. Tenemos un par de opciones aquí; podemos mirar la tabla (es decir, el índice agrupado), pero eso implicaría escanear todo, ya que la tabla está ordenada por la IDcolumna, que no nos dice nada sobre qué fila (s) contiene Testen la Datcolumna. La otra opción (y la elegida por SQL Server) consiste en buscar en el IX_Table1índice no agrupado para encontrar la fila donde Dat = 'Test', sin embargo, dado que también necesitamos la Othcolumna, SQL Server debe realizar una búsqueda en el índice agrupado utilizando una "Clave Operación de búsqueda ". Este es el plan para eso:

ingrese la descripción de la imagen aquí

Si modificamos el índice no agrupado para que incluya la Othcolumna:

DROP INDEX IX_Table1
ON dbo.Table1;
GO

CREATE INDEX IX_Table1
ON dbo.Table1 (Dat)
INCLUDE (Oth);        <---- This is the only change
GO

Luego vuelva a ejecutar la consulta:

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';

Ahora vemos una única búsqueda de índice no agrupado, ya que SQL Server simplemente necesita ubicar la fila Dat = 'Test'en el IX_Table1índice, que incluye el valor para Oth, y el valor de la IDcolumna (la clave primaria), que está presente automáticamente en cada índice agrupado El plan:

ingrese la descripción de la imagen aquí


6

La búsqueda de claves se debe a que el motor eligió usar un índice que no contiene todas las columnas que está tratando de recuperar. Por lo tanto, el índice no cubre las columnas en la instrucción select y where.

Para eliminar la búsqueda de clave, debe incluir las columnas que faltan (las columnas en la lista de resultados de la búsqueda de clave) = ProducerContactGuid, QuoteStatusID, PolicyTypeID y ProducerLocationID u otra forma es forzar a la consulta a utilizar el índice agrupado en su lugar.

Tenga en cuenta que 27 índices no agrupados en una tabla pueden ser malos para el rendimiento. Al ejecutar una actualización, insertar o eliminar, SQL Server debe actualizar todos los índices. Este trabajo adicional puede afectar negativamente el rendimiento.


También tenga en cuenta que demasiados índices pueden confundir la compilación del plan de ejecución y también pueden dar lugar a selecciones subóptimas.
Lopsided

4

Olvidó mencionar el volumen de datos involucrados en esta consulta. Además, ¿por qué te estás insertando en una tabla temporal? Si solo necesita mostrar, no ejecute una instrucción de inserción.

Para los fines de esta consulta, tblQuotesno necesita 27 índices no agrupados. Necesita 1 índice agrupado y 5 índices no agrupados o, quizás 6 indexex no agrupados.

Esta consulta quisiera índices en estas columnas:

QuoteStatusID
ProducerLocationID
ProducerContactGuid
EffectiveDate
LineGUID
OriginalQuoteGUID

También noté el siguiente código:

DATEDIFF(D, @EffDateFrom, tblQuotes.EffectiveDate) >= 0 AND 
DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <= 0

es NON Sargabledecir, no puede utilizar índices.

Para hacer que el código lo SARgablecambie a esto:

tblQuotes.EffectiveDate >= @EffDateFrom 
AND  tblQuotes.EffectiveDate <= @EffDateFrom

Para responder a su pregunta principal, "¿por qué está recibiendo una búsqueda clave?":

Está obteniendo KEY Look upporque parte de la columna que se menciona en la consulta no está presente en un índice de cobertura.

Puedes buscar en Google y estudiar sobre Covering Indexo Include index.

En mi ejemplo, supongamos que tblQuotes.QuoteStatusID es un índice no agrupado, entonces también puedo cubrir DisplayStatus. Como desea DisplayStatus en Resultset. Cualquier columna que no esté presente en un índice y esté presente en el conjunto de resultados se puede cubrir para evitar KEY Look Up or Bookmark lookup. Este es un ejemplo que cubre el índice:

create nonclustered index tblQuotes_QuoteStatusID 
on tblQuotes(QuoteStatusID)
include(DisplayStatus);

** Descargo de responsabilidad: ** Recuerde que lo anterior es solo mi ejemplo. DisplayStatus puede estar cubierto con otro No CI después del análisis.

Del mismo modo, deberá crear un índice y un índice de cobertura en las otras tablas involucradas en la consulta.

También te estás metiendo Index SCANen tu plan.

Esto puede suceder porque no hay un índice en la tabla o cuando hay un gran volumen de datos, el optimizador puede decidir escanear en lugar de realizar una búsqueda de índice.

Esto también puede ocurrir debido a High cardinality. Obteniendo más filas de las requeridas debido a una unión defectuosa. Esto también se puede corregir.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.