Consideraciones mayores
Veo una ventaja importante para los montones y otra para las tablas agrupadas, más una tercera consideración que puede ser de cualquier manera.
Un montón le ahorra una capa de indirección. Los índices contienen ID de fila, apuntando directamente (bueno, no realmente, pero lo más directamente posible) a una ubicación de disco. Por lo tanto, una búsqueda de índice contra un montón debería costar aproximadamente la mitad de una búsqueda de índice no agrupada contra una tabla agrupada.
Un índice agrupado se ordena, per se, gracias a un índice (casi) libre. Debido a que el índice de agrupación se refleja en el orden físico de los datos, ocupa relativamente poco espacio en la parte superior de los datos reales, que por supuesto tiene que almacenar de todos modos. Debido a que está físicamente ordenado, un escaneo de rango contra este índice puede buscar el punto de inicio y luego avanzar hasta el punto final de manera muy eficiente.
Los índices en los montones hacen referencia a los RID, que son de 64 bits. Como se mencionó, los índices no agrupados en una tabla agrupada hacen referencia a la clave de agrupación, que puede ser más pequeña (una de 32 bits INT
), la misma (una de 64 bits BIGINT
) o más grande (una de 48 bits DATETIME2()
más una de 32 bits INT
, o un GUID de 128 bits). Obviamente, una referencia más amplia genera índices más grandes y más caros.
Requerimientos de espacio
Con estas dos tablas:
CREATE TABLE TmpClustered
(
ID1 INT NOT NULL,
ID2 INT NOT NULL
)
ALTER TABLE TmpClustered ADD CONSTRAINT PK_Tmp1 PRIMARY KEY CLUSTERED (ID1)
CREATE UNIQUE INDEX UQ_Tmp1 ON TmpClustered (ID2)
CREATE TABLE TmpNonClustered
(
ID1 INT NOT NULL,
ID2 INT NOT NULL
)
ALTER TABLE TmpNonClustered ADD CONSTRAINT PK_Tmp2 PRIMARY KEY NONCLUSTERED (ID1)
CREATE UNIQUE INDEX UQ_Tmp2 ON TmpNonClustered (ID2)
... cada uno con 8,7 M de registros, el espacio requerido era de 150 MB para los datos de ambos; 120 MB para los índices de la tabla agrupada, 310 MB para los índices de la tabla no agrupada. Esto refleja que el índice agrupado es más angosto que un RID, y que el índice agrupado es principalmente un "regalo de promoción". Sin los índices únicos ID2
, el espacio de índice requerido cae a 155 MB para la tabla no agrupada (la mitad, como era de esperar) pero solo 150 KB para la PK agrupada, casi nada.
Por lo tanto, un índice no agrupado de un campo de 32 bits en una tabla agrupada con un índice de 32 bits (total de 64 bits, nominalmente) tomó 120 MB, mientras que un índice de un campo de 32 bits en un montón de 64 bits RID (total de 96 bits, nominalmente) tomó 155 MB, un poco menos del aumento del 50% que uno esperaría ingenuamente pasar de claves de 64 bits a 96 bits, pero por supuesto hay una sobrecarga que reduce la diferencia efectiva de tamaño.
Completar las dos tablas y crear sus índices tomó la misma cantidad de tiempo para cada tabla. Al ejecutar pruebas simples que involucran escaneos o búsquedas, no encontré diferencias materiales de rendimiento entre las tablas, lo que coincide con el documento técnico de Microsoft que gbn ayudó de manera útil. Dicho documento muestra una diferencia significativa para el acceso altamente concurrente; No estoy seguro de por qué sucede eso, espero que alguien con más experiencia que yo con sistemas OLTP de alto volumen pueda decirnos.
Agregar ~ 40 bytes de datos aleatorios de longitud variable no cambió apreciablemente esta equivalencia. Reemplazar los INT
s con UUID anchos tampoco (cada tabla se desaceleró aproximadamente en la misma medida). Su millaje puede variar, pero en la mayoría de los casos si un índice está disponible es más importante que de qué tipo.
Pedazos y pedazos
Hacer un escaneo de rango contra un índice no agrupado, ya sea porque la tabla es un montón o porque el índice no es el índice agrupado, implica escanear el índice y luego buscar en la tabla para cada golpe. Esto puede ser muy costoso, por lo que a veces es más barato escanear la tabla. Sin embargo, puede solucionar esto con un índice de cobertura. Esto se aplica tanto si ha agrupado su tabla como si no.
Como señaló @gbn, no hay una manera simple de compactar un montón. Sin embargo, si su tabla aumenta gradualmente con el tiempo, un caso muy común, habrá poco desperdicio ya que el espacio liberado por las eliminaciones se llenará con nuevos datos.
Varias de las discusiones del montón frente a la tabla agrupada que he visto hacen un curioso argumento de que un montón sin índices es inferior a una tabla agrupada ya que siempre requiere un escaneo de la tabla. Esto es ciertamente cierto, pero la comparación más significativa es "gran tabla agrupada bien indexada" versus "gran montón bien indexado". Si su tabla es muy pequeña o siempre va a hacer escaneos de tablas, entonces no importa mucho si la agrupa o no.
Debido a que cada índice en una tabla agrupada hace referencia al índice de agrupación, en realidad son todos índices de cobertura. Una consulta que hace referencia a una columna indexada y a la (s) columna (s) de agrupación puede realizar una exploración de índice sin ninguna búsqueda en la tabla. Esto generalmente no es valioso si su índice de agrupación es una clave sintética, pero si se trata de una clave comercial que necesitaría recuperar de todos modos, es una buena característica.
TL; DR
Soy un chico de almacenamiento de datos, no un experto en OLTP. Para las tablas de hechos, casi siempre utilizo un índice de agrupación en el campo que probablemente necesite escaneos de rango, generalmente un campo de fecha. Para las tablas de dimensiones, me agrupo en la PK, por lo que está clasificada previamente para combinar combinaciones con tablas de hechos.
Existen varias razones para usar índices de agrupamiento, pero si ninguna de esas razones se aplica, entonces la sobrecarga puede no valer la pena. Sospecho que hay un montón de "siempre lo hemos hecho de esta manera" y "es la mejor práctica" detrás de las personas que usan índices agrupados universalmente. Pruebe ambos con sus datos y su carga y vea qué funciona mejor.