¿Es incorrecta la regla WHERE-JOIN-ORDER- (SELECT) para el orden de la columna de índice?


9

Estoy tratando de mejorar esta (sub) consulta como parte de una consulta más grande:

select SUM(isnull(IP.Q, 0)) as Q, 
        IP.OPID 
    from IP
        inner join I
        on I.ID = IP.IID
    where 
        IP.Deleted=0 and
        (I.Status > 0 AND I.Status <= 19) 
    group by IP.OPID

Sentry Plan Explorer señaló algunas búsquedas de claves relativamente caras para la tabla dbo. [I] realizada por la consulta anterior.

Tabla dbo.I

    CREATE TABLE [dbo].[I] (
  [ID]  UNIQUEIDENTIFIER NOT NULL,
  [OID]  UNIQUEIDENTIFIER NOT NULL,
  []  UNIQUEIDENTIFIER NOT NULL,
  []  UNIQUEIDENTIFIER NOT NULL,
  [] UNIQUEIDENTIFIER NULL,
  []  UNIQUEIDENTIFIER NOT NULL,
  []  CHAR (3) NOT NULL,
  []  CHAR (3)  DEFAULT ('EUR') NOT NULL,
  []  DECIMAL (18, 8) DEFAULT ((1)) NOT NULL,
  [] CHAR (10)  NOT NULL,
  []  DECIMAL (18, 8) DEFAULT ((1)) NOT NULL,
  []  DATETIME  DEFAULT (getdate()) NOT NULL,
  []  VARCHAR (35) NULL,
  [] NVARCHAR (100) NOT NULL,
  []  NVARCHAR (100) NULL,
  []  NTEXT  NULL,
  []  NTEXT  NULL,
  []  NTEXT  NULL,
  []  NTEXT  NULL,
  [Status]  INT DEFAULT ((0)) NOT NULL,
  []  DECIMAL (18, 2)  NOT NULL,
  [] DECIMAL (18, 2)  NOT NULL,
  [] DECIMAL (18, 2)  NOT NULL,
  [] DATETIME DEFAULT (getdate()) NULL,
  []  DATETIME NULL,
  []  NTEXT  NULL,
  []  NTEXT  NULL,
  [] TINYINT  DEFAULT ((0)) NOT NULL,
  []  DATETIME NULL,
  []  VARCHAR (50) NULL,
  []  DATETIME  DEFAULT (getdate()) NOT NULL,
  []  VARCHAR (50) NOT NULL,
  []  DATETIME NULL,
  []  VARCHAR (50) NULL,
  []  ROWVERSION NOT NULL,
  []  DATETIME NULL,
  []  INT  NULL,
  [] TINYINT DEFAULT ((0)) NOT NULL,
  [] UNIQUEIDENTIFIER NULL,
  []  TINYINT DEFAULT ((0)) NOT NULL,
  []  TINYINT  DEFAULT ((0)) NOT NULL,
  [] NVARCHAR (50)  NULL,
  [] TINYINT DEFAULT ((0)) NOT NULL,
  []  UNIQUEIDENTIFIER NULL,
  [] UNIQUEIDENTIFIER NULL,
  []  TINYINT  DEFAULT ((0)) NOT NULL,
  []  TINYINT DEFAULT ((0)) NOT NULL,
  []  UNIQUEIDENTIFIER NULL,
  []  DECIMAL (18, 2)  NULL,
  []  DECIMAL (18, 2)  NULL,
  [] DECIMAL (18, 2)  DEFAULT ((0)) NOT NULL,
  [] UNIQUEIDENTIFIER NULL,
  [] DATETIME NULL,
  [] DATETIME NULL,
  []  VARCHAR (35) NULL,
  [] DECIMAL (18, 2)  DEFAULT ((0)) NOT NULL,
  CONSTRAINT [PK_I] PRIMARY KEY NONCLUSTERED ([ID] ASC) WITH (FILLFACTOR = 90),
  CONSTRAINT [FK_I_O] FOREIGN KEY ([OID]) REFERENCES [dbo].[O] ([ID]),
  CONSTRAINT [FK_I_Status] FOREIGN KEY ([Status]) REFERENCES [dbo].[T_Status] ([Status])
);                  


GO
CREATE CLUSTERED INDEX [CIX_Invoice]
  ON [dbo].[I]([OID] ASC) WITH (FILLFACTOR = 90);

Tabla dbo.IP

CREATE TABLE [dbo].[IP] (
 [ID] UNIQUEIDENTIFIER DEFAULT (newid()) NOT NULL,
 [IID] UNIQUEIDENTIFIER NOT NULL,
 [OID] UNIQUEIDENTIFIER NOT NULL,
 [Deleted] TINYINT DEFAULT ((0)) NOT NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 []UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] INT NOT NULL,
 [] VARCHAR (35) NULL,
 [] NVARCHAR (100) NOT NULL,
 [] NTEXT NULL,
 [] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
 [] NTEXT NULL,
 [] NTEXT NULL,
 [] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
 [] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
 [] DECIMAL (4, 2) NOT NULL,
 [] INT DEFAULT ((1)) NOT NULL,
 [] DATETIME DEFAULT (getdate()) NOT NULL,
 [] VARCHAR (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [] DATETIME NULL,
 [] VARCHAR (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
 [] ROWVERSION NOT NULL,
 [] INT DEFAULT ((1)) NOT NULL,
 [] DATETIME NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] DECIMAL (18, 4) DEFAULT ((1)) NOT NULL,
 [] DECIMAL (18, 4) DEFAULT ((1)) NOT NULL,
 [] INT DEFAULT ((0)) NOT NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 []UNIQUEIDENTIFIER NULL,
 []NVARCHAR (35) NULL,
 [] VARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] VARCHAR (12) NULL,
 [] VARCHAR (4) NULL,
 [] NVARCHAR (50) NULL,
 [] NVARCHAR (50) NULL,
 [] VARCHAR (35) NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] TINYINT DEFAULT ((0)) NOT NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] TINYINT DEFAULT ((0)) NOT NULL,
 [] TINYINT DEFAULT ((0)) NOT NULL,
 [] NVARCHAR (50) NULL,
 [] TINYINT DEFAULT ((0)) NOT NULL,
 [] DECIMAL (18, 2) NULL,
 []TINYINT DEFAULT ((1)) NOT NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] TINYINT DEFAULT ((1)) NOT NULL,
 CONSTRAINT [PK_IP] PRIMARY KEY NONCLUSTERED ([ID] ASC) WITH (FILLFACTOR = 90),
 CONSTRAINT [FK_IP_I] FOREIGN KEY ([IID]) REFERENCES [dbo].[I] ([ID]) ON DELETE CASCADE NOT FOR REPLICATION,
 CONSTRAINT [FK_IP_XType] FOREIGN KEY ([XType]) REFERENCES [dbo].[xTYPE] ([Value]) NOT FOR REPLICATION
);

GO
CREATE CLUSTERED INDEX [IX_IP_CLUST]
 ON [dbo].[IP]([IID] ASC) WITH (FILLFACTOR = 90);

La tabla "I" tiene aproximadamente 100,000 filas, el índice agrupado tiene 9,386 páginas.
La tabla IP es la tabla "secundaria" de I y tiene aproximadamente 175,000 filas.

Traté de agregar un nuevo índice siguiendo la regla de orden de la columna de índice: "WHERE-JOIN-ORDER- (SELECT)"

https://www.mssqltips.com/sqlservertutorial/3208/use-where-join-orderby-select-column-order-when-creating-indexes/

para abordar las búsquedas clave y crear una búsqueda de índice:

CREATE NONCLUSTERED INDEX [IX_I_Status_1]
    ON [dbo].[Invoice]([Status], [ID])

La consulta extraída usó inmediatamente este índice. Pero la consulta original más grande de la que forma parte no lo hizo. Ni siquiera lo usó cuando lo forcé a usar WITH (INDEX (IX_I_Status_1)).

Después de un tiempo decidí probar otro índice nuevo y cambié el orden de las columnas indexadas:

CREATE NONCLUSTERED INDEX [IX_I_Status_2]
    ON [dbo].[Invoice]([ID], [Status])

WOHA! Este índice fue utilizado por la consulta extraída y también por la consulta más grande.

Luego comparé las estadísticas extraídas de IO de las consultas forzándolas a usar [IX_I_Status_1] y [IX_I_Status_2]:

Resultados [IX_I_Status_1]:

Table 'I'. Scan count 5, logical reads 636, physical reads 16, read-ahead reads 574
Table 'IP'. Scan count 5, logical reads 1134, physical reads 11, read-ahead reads 1040
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0

Resultados [IX_I_Status_2]:

Table 'I'. Scan count 1, logical reads 615, physical reads 6, read-ahead reads 631
Table 'IP'. Scan count 1, logical reads 1024, physical reads 5, read-ahead reads 1040
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0,  read-ahead reads 0

Bien, podría entender que la consulta de mega-monstruos grandes puede ser demasiado compleja para hacer que el servidor SQL capte el plan de ejecución ideal y puede perder mi nuevo índice. Pero no entiendo por qué el índice [IX_I_Status_2] parece ser más adecuado y más eficiente para la consulta.

Dado que la consulta, en primer lugar, filtra la tabla I por la columna ESTADO y luego se une con la tabla IP, no entiendo por qué [IX_I_Status_2] es mejor y lo usa Sql Server en lugar de [IX_I_Status_1].


Sí, utiliza este índice en caso de que cumplan los criterios de filtro. Realiza una exploración de índice (igual que con IX_I_Status_2) y, en comparación con esto, guarda 1 lectura física. pero tuve que "incluir (estado)" en este índice porque el estado está en la salida y se volvió a buscar antes.
Magier

Nota al margen: después de aplicar el mejor índice que pude averiguar ([IX_I_Status_2]) y ejecutar la consulta nuevamente, ahora recibo una sugerencia de índice faltante: CREAR ÍNDICE NO CLUSTRADO [<Nombre del índice faltante, sysname,>] ON [ dbo]. [I] ([Estado]) INCLUDE ([ID]) Esta es una sugerencia pobre y disminuye el rendimiento de la consulta. Servidor TY Sql :)
Magier

Respuestas:


19

¿Es incorrecta la regla WHERE-JOIN-ORDER- (SELECT) para el orden de la columna de índice?

Por lo menos es un consejo incompleto y potencialmente engañoso (no me molesté en leer el artículo completo). Si va a leer cosas en Internet (incluido esto), debe ajustar su nivel de confianza de acuerdo con lo bien que ya conoce y confía en el autor, pero siempre verifique por usted mismo.

Hay una serie de "reglas generales" para crear índices, dependiendo del escenario exacto, pero ninguna es realmente un buen sustituto para comprender los problemas centrales por sí mismo. Lea sobre la implementación de índices y operadores de planes de ejecución en SQL Server, realice algunos ejercicios y comprenda de manera sólida y sólida cómo se pueden usar los índices para hacer que los planes de ejecución sean más eficientes. No existe un atajo efectivo para lograr este conocimiento y experiencia.

En general, puedo decir que sus índices deberían tener columnas con mayor frecuencia utilizadas para pruebas de igualdad primero, con cualquier desigualdad al final, y / o proporcionadas por un filtro en el índice. Esta no es una declaración completa, porque los índices también pueden proporcionar orden, lo que puede ser más útil que buscar directamente una o más claves en algunas situaciones. Por ejemplo, el pedido se puede utilizar para evitar una ordenación, para reducir el costo de una opción de combinación física como la combinación de combinación, para habilitar un agregado de flujo, encontrar las primeras filas calificadas rápidamente ... y así sucesivamente.

Estoy siendo un poco vago aquí, porque seleccionar los índices ideales para una consulta depende de muchos factores, este es un tema muy amplio.

De todos modos, no es inusual encontrar señales conflictivas para los 'mejores' índices en una consulta. Por ejemplo, su predicado de unión quisiera filas ordenadas de una manera para una combinación de fusión, el grupo por quisiera filas ordenadas de otra manera para un agregado de flujo, y encontrar las filas calificadas usando los predicados de la cláusula where sugeriría otros índices.

La razón por la que la indexación es tanto un arte como una ciencia es que una combinación ideal no siempre es lógicamente posible. Elegir los mejores índices de compromiso para la carga de trabajo (no solo una sola consulta) requiere habilidades analíticas, experiencia y conocimiento específico del sistema. Si fuera fácil , las herramientas automatizadas serían perfectas, y los consultores de ajuste de rendimiento tendrían mucha menos demanda.

En cuanto a las sugerencias de índice que faltan: son oportunistas. El optimizador los llama su atención cuando intenta hacer coincidir los predicados y el orden de clasificación requerido con un índice que no existe. Por lo tanto, las sugerencias se basan en intentos particulares de emparejamiento en el contexto específico de la variación particular del sub-plan que estaba considerando en ese momento.

En contexto, las sugerencias siempre tienen sentido, en términos de reducir el costo estimado de acceso a datos, de acuerdo con el modelo del optimizador. No no hacer un análisis más amplio de la consulta en su conjunto (y mucho menos la carga de trabajo más amplio), por lo que debe pensar en estas sugerencias como un toque suave que necesita un experto para mirar los índices disponibles, con las sugerencias como una partida punto (y generalmente no más que eso).

En su caso, la (Status) INCLUDE (ID)sugerencia probablemente surgió cuando se analizó la posibilidad de un hash o una combinación (ejemplo más adelante). En ese contexto limitado, la sugerencia tiene sentido. Para la consulta en su conjunto, tal vez no. El índice (ID, Status)permite una unión de bucle anidado IDcomo referencia externa: búsqueda de igualdad IDy desigualdad Statuspor iteración.

Una posible selección de índices es:

CREATE INDEX i1 ON dbo.I (ID, [Status]);
CREATE INDEX i1 ON dbo.IP (Deleted, OPID, IID) INCLUDE (Q);

... que produce un plan como:

Posible plan

No digo que estos índices sean óptimos para usted; funcionan para producir un plan de aspecto razonable para mí, sin poder ver estadísticas de las tablas involucradas, o las definiciones completas y la indexación existente. Además, no sé nada de la carga de trabajo más amplia o consulta real.

Alternativamente (solo para mostrar una de las innumerables posibilidades adicionales):

CREATE INDEX i1 ON dbo.I ([Status]) INCLUDE (ID);
CREATE INDEX i1 ON dbo.IP (Deleted, IID, OPID) INCLUDE (Q);

Da:

Plan alternativo

Los planes de ejecución se generaron utilizando SQL Sentry Plan Explorer .

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.