Los índices en la base de datos SQL Server es una herramienta que nos ayuda a mejorar la respuesta de nuestras consultas, pero un problema muy recurrente es tener índices repetidos o similares, que nos puede llegar a ocacionar en la mayoría de los casos problemas de rendimiento o que el motor de la base de datos ni lo toma en cuenta.
En este artículo te explico como encontrar esos índices duplicados para analizar que medidas tomar, ya sea modificarlos o eliminar los que no estemos utilizando.
[display-posts category=”sql-server” posts_per_page=”1″ exclude_current=”true” wrapper=”div” image_size=”thumbnail” include_excerpt=”true” excerpt_length=”15″]
¿Por qué tenemos índices duplicados?
Pueden existir distintas razones, te detallamos las más comunes:
- No existe alguien responsable de la base de datos, un DBA, y cada desarrollador crea los índices que el cree necesarios, y así cada desarrollador por separado, lo que lleva a acumular índices que se repiten, muchos no se terminan utilizando.
- Utilizar herramientas de análisis de consultas, las cuales hacen recomendaciones de índices y crearlos sin analizar si ya existen similares. Las herramientas de optimización de consultas son de mucha ayuda, pero hay que utilizarlos con cuidado y mucho análisis para saber cuales índices aplicar.
¿Cuáles problemas podemos tener con índices similares o repetidos?
Los índices repetidos parcial o total nos pueden generar una serie de problemas:
- Problema de rendimiento, al tener que actualizar los índices en cada insert, update o delete, el motor de la base de datos utiliza recursos extra.
- Al hacer las consultas SQL debe analizar los índices a utilizar, lo que puede ocacionar una sobrecarga inecesaria.
- Vamos a tener backups de mayor tamaño, el motor de la base de datos por cada índice almacena los datos (como si fuera una tabla) para retornarlos en nuestras consultas, por lo que si tenemos índices repetidos vamos a tener la información duplicada, y si estas tablas son de gran tamaño el consumo puede ser un problema de megas o gigas que nos podemos ahorrar.
- Transaction Log de gran tamaño, ocacionado por la actualización de los índices, que nos puede ocacionar problemas de espacio en disco. Aumentado si no tenemos un correcto plan de backups de nuestras bases de datos.
- Sobrecarga del servidor al reorganizar o reconstruir índices.
¿Cómo encontrar índices repetidos?
Vamos a tomar como ejemplo la siguiente tabla
CREATE TABLE [dbo].[Producto]( [codigoProducto] [int] NOT NULL, [codigoBarra] [varchar](20) NOT NULL, [codigoCategoria] [int] NOT NULL, [nombreProducto] [varchar](100) NOT NULL, [fechaRegistro] [datetime] NOT NULL, [fechaActualizado] [datetime] NOT NULL, [activo] [tinyint] NOT NULL, [precio] [money] NOT NULL, [cuentaContable] [varchar](20) NOT NULL, ) GO ALTER TABLE [dbo].[Producto] ADD CONSTRAINT [PK_Producto] PRIMARY KEY CLUSTERED ([codigoProducto]) GO
SQL de forma automática nos va a crear el índice para la llave primaria codigoProducto.
Para el ejemplo vamos a tener los siguientes índices, 2 iguales y los otros similares. Este es un ejemplo extremo, pero necesitamos bastantes ejemplos para entender en análisis más facilmente.
- IX_Producto_cProducto_cBarra
- IX_Producto_cProducto_cBarra_codCategoria
- IX_Producto_cProd_cBarra_cCategoria
- IX_Producto_cProducto_cBarra_activo
Por el nombre de los índices podemos notar que codigoProducto y codigoBarra lo repetimos en todos los índices, los otros índices agegan columnas a los otros índices.
Solución
Después de buscar en internet varios ejemplos adapté dos ejemplos para nos requerimientos. Links a los blogs al final del post.
El siguiente script nos va a retornar informacion por tabla, índice, columnas compartidas. Debe de retornar una información similar a la imagen siguiente.

Como podemos ver nos retorna el nombre del índice, agrupado por la tabla para tener más orden, cuales son las columnas en conflicto y las columnas que utiliza cada indice, indicando cuales están repetidas.
El siguiente script es una base de trabajo y lo puedes modificar según tus requerimientos.
WITH BaseNumbers AS (
SELECT 1 AS ColId
UNION ALL
SELECT ColId+1
FROM BaseNumbers
--Columnas maximas por indice por analizar
--WHERE ColId<32
)
, IndexColumns AS (
SELECT Sch.name AS SchemaName,
objetos.name AS TableName,
indices.name AS IndexName,
indices.type_desc AS IndexType,
indices.index_id,
Idc.key_ordinal,
Col.name AS ColumnName
FROM sys.indexes AS indices
INNER JOIN sys.objects AS objetos
ON indices.object_id = objetos.object_id
INNER JOIN sys.schemas AS SCH
ON objetos.schema_id = SCH.schema_id
JOIN sys.index_columns AS Idc
ON indices.index_id = Idc.index_id
AND indices.object_id = Idc.object_id
INNER JOIN sys.columns AS Col
ON Col.column_id = Idc.column_id
AND Col.object_id = Idc.object_id
WHERE indices.index_id > 0 --- Not MS Products
--- Not MS Products
AND objetos.is_ms_shipped=0
--- Solo Tablas
AND objetos.type in ('U ')
--- CLUSTERED, NONCLUSTERED AND NONCLUSTERED HASH
AND indices.type IN (1,2,7)
--- Sin included columns
AND Idc.is_included_column=0
)
, IndexColumnsAgg AS (
SELECT SchemaName, TableName, IndexName, IndexType, index_id, key_ordinal AS NumeroColumna
, CAST(ColumnName AS VARCHAR(MAX)) AS IndexColumns
FROM IndexColumns
WHERE key_ordinal=1
UNION ALL
SELECT AGG.SchemaName, AGG.TableName, AGG.IndexName, AGG.IndexType, IC.index_id AS NumeroColumna, IC.key_ordinal
, CAST(CONCAT(AGG.IndexColumns, ', ', ColumnName) AS VARCHAR(MAX)) AS IndexColumns
FROM IndexColumnsAgg AS AGG
JOIN IndexColumns AS IC
ON AGG.SchemaName=IC.SchemaName
AND AGG.TableName=IC.TableName
AND AGG.IndexName=IC.IndexName
AND AGG.NumeroColumna+1=IC.key_ordinal
)
, IndexColumnsAggExt AS (
SELECT *,
LAST_VALUE(NumeroColumna)
OVER(PARTITION BY SchemaName, TableName, IndexName ORDER BY NumeroColumna ASC
RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS TotalCantidadColumnas
, LAST_VALUE(IndexColumns)
OVER(PARTITION BY SchemaName, TableName, IndexName ORDER BY NumeroColumna ASC
RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS FullIndexColumns
FROM IndexColumnsAgg
)
, IndexPairs AS (
SELECT L.SchemaName AS 'Schema'
, L.TableName AS Nombretabla
, L.IndexName AS PrimerIndice
, R.IndexName AS SegundoIndice
, L.FullIndexColumns AS PrimerIndiceColumnas
, R.FullIndexColumns AS SegundoIndiceColumnas
, L.IndexColumns AS ColumnasCompartidas
, L.IndexType AS PrimerTipoIndice
, R.IndexType AS SegundoTipoIndice
, L.TotalCantidadColumnas AS CantColumnasPrimerIndice
, R.TotalCantidadColumnas AS CantColumnasSegundoIndice
, L.NumeroColumna AS CantidadColumnasCompartidas
, CASE WHEN L.TotalCantidadColumnas > R.TotalCantidadColumnas
THEN L.TotalCantidadColumnas
ELSE R.TotalCantidadColumnas END AS CantidadMaximaColumnas
, ROW_NUMBER() OVER(PARTITION BY L.SchemaName, L.TableName, L.IndexName, R.IndexName
ORDER BY L.NumeroColumna DESC) AS Pos
FROM IndexColumnsAggExt AS L
JOIN IndexColumnsAggExt AS R
ON L.SchemaName = R.SchemaName
AND L.TableName = R.TableName
AND L.IndexColumns = R.IndexColumns
AND L.NumeroColumna = R.NumeroColumna
AND L.index_id < R.index_id
)
SELECT C.Criteria as 'Criterio',
IndexPairs.*
FROM IndexPairs
CROSS APPLY (SELECT
CASE
WHEN CantidadColumnasCompartidas = CantidadMaximaColumnas
THEN 'Iguales'
WHEN CantColumnasPrimerIndice = CantidadMaximaColumnas OR
CantColumnasSegundoIndice = CantidadMaximaColumnas
THEN 'Similares'
ELSE 'Compartidas'
END AS Criteria) AS C
WHERE Pos=1
ORDER BY Nombretabla, PrimerIndice, Criterio
Espero que el artículo te sea de utilidad y no dudes en escribirme sobre consultas de este artículo y sobre cualquier otro tema de SQL Server.
Frases relacionadas:
- Problema con índices en SQL Server
- Rendimiento con índices en SQL Server
- SQL Server no utiliza mi índice
- ¿Los índices consumen espacio en disco?
- Índices clustered y nonclustered SQL Server
Referencias:
