¿El índice no agrupado es más rápido que el índice agrupado?

9

Ambas tablas tienen la misma estructura y 19972 filas en cada tabla. para practicar la indexación, creé ambas tablas con la misma estructura y creé

clustered index on persontb(BusinessEntityID)

y

nonclustered index on Persontb_NC(BusinessEntityId)

y estructura de la mesa

BusinessEntityID int
FirstName varchar(100)
LastName  varchar(100)                                                                                                                       

 -- Nonclusted key on businessentityid takes 38%
SELECT  BusinessEntityId from Persontb_NC
WHERE businessentityid BETWEEN 400 AND 4000

-- CLustered key businessentityid takes 62%
SELECT BusinessEntityId  from persontb 
WHERE businessentityid BETWEEN 400 AND 4000

ingrese la descripción de la imagen aquí

¿Por qué el índice agrupado toma 62% y el 38% no agrupado?


fuente
1
¿Por qué votar por el cierre?

Respuestas:

10

Sí, el índice agrupado tiene menos filas por página que el índice no agrupado ya que las páginas de hoja del índice agrupado deben almacenar los valores para las otras dos columnas ( FirstNamey LastName).

Las páginas de hoja del NCI almacenan solo los BusinessEntityIdvalores y un localizador de filas (RID si la tabla es un montón o la clave CI de lo contrario).

Por lo tanto, los costos estimados reflejan el mayor número de lecturas y el requisito de IO.

Si declararas al NCI como

nonclustered index on Persontb_NC(BusinessEntityId) INCLUDE (FirstName, LastName)

entonces sería similar al índice agrupado.

Martin Smith
fuente
5

El índice agrupado contiene no solo datos del índice de columna activado, sino también datos de todas las demás columnas. (Solo puede haber un índice agrupado por tabla)

El índice no agrupado contiene solo datos de la (s) columna (s) indexada (s) y un puntero row_id a donde está el resto de los datos.

Por lo tanto, este índice no agrupado en particular es más ligero y se requiere menos lectura para escanearlo / buscarlo y esta consulta en particular funcionará más rápido.

Sin embargo, si ha intentado recuperar FirstName y LastName también, sería diferente y el índice agrupado debería funcionar mejor.

Nenad Zivkovic
fuente
2

Los porcentajes entre los planes de consulta no tienen sentido para comparar directamente. Debe comparar las consultas para tener una comparación válida. Además, los recuentos de filas pequeñas tienden a ocultar las diferencias de rendimiento entre las estrategias de indexación. Al aumentar el recuento de filas a 10 millones, puede obtener una imagen más clara de las diferencias de rendimiento.

Hay un script de muestra que crea 3 tablas, las dos de arriba y una tercera con un índice agrupado y no agrupado.

USE [tempdb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[t1](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [c1] [varchar](200) NULL
) ON [PRIMARY]

CREATE TABLE [dbo].[t2](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [c1] [varchar](200) NULL
) ON [PRIMARY]

CREATE TABLE [dbo].[t3](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [c1] [varchar](200) NULL
) ON [PRIMARY]

GO

CREATE CLUSTERED INDEX CIX_t1 ON t1(id)

CREATE NONCLUSTERED INDEX IX_t2 ON t2(id)

CREATE CLUSTERED INDEX CIX_t3 ON t3(id)
CREATE NONCLUSTERED INDEX IX_t3 ON t3(id)

Rellene las tablas con 10 millones de filas.

DECLARE @i INT
DECLARE @j int
DECLARE @t DATETIME
SET NOCOUNT ON
SET @t = CURRENT_TIMESTAMP
SET @i = 0
WHILE @i < 10000000
BEGIN
--populate with strings with a length between 100 and 200 
INSERT INTO t1 (c1) VALUES (REPLICATE('x', 101+ CAST(RAND(@i) * 100 AS INT)))
SET @i = @i + 1
END

PRINT 'Time to populate t1: '+ CAST(DATEDIFF(ms, @t, CURRENT_TIMESTAMP) AS VARCHAR(10)) + ' ms'
SET @t = CURRENT_TIMESTAMP


SET @i = 0
WHILE @i < 10000000
BEGIN
--populate with strings with a length between 100 and 200 
INSERT INTO t2 (c1) VALUES (REPLICATE('x', 101+ CAST(RAND(@i) * 100 AS INT)))
SET @i = @i + 1
END

PRINT 'Time to populate t3: '+ CAST(DATEDIFF(ms, @t, CURRENT_TIMESTAMP) AS VARCHAR(10)) + ' ms'
SET @t = CURRENT_TIMESTAMP

SET @i = 0
WHILE @i < 10000000
BEGIN
--populate with strings with a length between 100 and 200 
INSERT INTO t3 (c1) VALUES (REPLICATE('x', 101+ CAST(RAND(@i) * 100 AS INT)))
SET @i = @i + 1
END

PRINT 'Time to populate t3: '+ CAST(DATEDIFF(ms, @t, CURRENT_TIMESTAMP) AS VARCHAR(10)) + ' ms'

Podemos usar sys.dm_db_index_physical_stats para ver el tamaño en el disco de los índices.

SELECT  OBJECT_NAME(OBJECT_ID) table_name, index_id, index_type_desc, 
record_count, page_count, page_count / 128.0 size_in_mb, avg_record_size_in_bytes
FROM    sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('t1'), NULL, NULL, 'detailed')
WHERE   index_level = 0 
UNION ALL
SELECT  OBJECT_NAME(OBJECT_ID) table_name, index_id, index_type_desc, 
record_count, page_count, page_count / 128.0 size_in_mb, avg_record_size_in_bytes
FROM    sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('t2'), NULL, NULL, 'detailed')
WHERE   index_level = 0 
UNION ALL
SELECT  OBJECT_NAME(OBJECT_ID) table_name, index_id, index_type_desc, 
record_count, page_count, page_count / 128.0 size_in_mb, avg_record_size_in_bytes
FROM    sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('t3'), NULL, NULL, 'detailed')
WHERE   index_level = 0 

Y los resultados:

table_name  index_id    page_count  size_in_mb  avg_record_size_in_bytes    index_type_desc
t1  1   211698  1653.890625 167.543 CLUSTERED INDEX
t2  0   209163  1634.085937 165.543 HEAP
t2  2   22272   174.000000  16  NONCLUSTERED INDEX
t3  1   211698  1653.890625 167.543 CLUSTERED INDEX
t3  2   12361   96.570312   8   NONCLUSTERED INDEX

El índice agrupado de T1 es de alrededor de 1,6 GB de tamaño. El índice no agrupado de T2 es de 170 MB (90% de ahorro en IO). El índice no agrupado de T3 es de 97 MB, o aproximadamente un 95% menos de IO que T1.

Entonces, basado en el IO requerido, el plan de consulta original debería haber estado más en la línea del 10% / 90%, no del 38% / 62%. Además, dado que es probable que el índice no agrupado se ajuste completamente en la memoria, la diferencia puede ser aún mayor, ya que el disco IO es muy costoso.

StrayCatDBA
fuente
1
Es un gran salto inferir que su 10%/90%figura es más precisa que la 38%/62%. Las cadenas con una longitud entre 100 y 200 serán una gran sobreestimación de los requisitos de espacio para un par de nombre / apellido, por lo que tendrá una densidad de página menor que el OP. Cuando intento contra sus datos de ejemplo, los costos estimados se muestran como 87% / 13% .
Martin Smith
1
SQL Server ya hace referencia a data_pagesin sys.allocation_units. Puede ver esto desde CREATE TABLE T1(C INT);CREATE TABLE T2(C INT);UPDATE STATISTICS T1 WITH PAGECOUNT = 1;UPDATE STATISTICS T2 WITH PAGECOUNT = 100entonces comparando los costos estimadosSELECT * FROM T1;SELECT * FROM T2;
Martin Smith
Vuelva a leer la primera oración de mi respuesta. Comparar costos directamente no tiene sentido. Para la diferencia de rendimiento entre las consultas del OP, se puede obtener una mejor estimación empíricamente calculando la reducción en el tamaño de los índices (y, por lo tanto, el número de E / S), no por los costos del optimizador.
StrayCatDBA
1
En términos generales, es sí, pero en este caso la razón por la cual el optimizador de consultas cuesta más el índice agrupado que el índice no agrupado (el tema de esta pregunta) se debe precisamente a los diferentes recuentos de páginas.
Martin Smith
1
Según http://www.qdpma.com/ppt/CostFormulas2.ppt la fórmula utilizada para costar una exploración de índice o búsqueda de índice sin búsqueda es (versión dependiente) IO (0,003125 + 0,00074074 por página) y la CPU (0,0001581 0,0000011 + por fila). Los costos fijos y las filas son iguales para CI y NCI, por lo que la única variable son las páginas.
Martin Smith