Rendimiento de índices no agrupados en montones frente a índices agrupados


39

Este Libro Blanco de 2007 compara el rendimiento de las declaraciones individuales de selección / inserción / eliminación / actualización y selección de rango en una tabla organizada como un índice agrupado frente a la de una tabla organizada como un montón con un índice no agrupado en las mismas columnas clave que el CI mesa.

En general, la opción de índice agrupado funcionó mejor en las pruebas, ya que solo hay una estructura para mantener y porque no hay necesidad de búsquedas de marcadores.

Un caso potencialmente interesante no cubierto por el documento habría sido una comparación entre un índice no agrupado en un montón frente a un índice no agrupado en un índice agrupado. En ese caso, hubiera esperado que el montón incluso funcionara mejor ya que una vez en el nivel de hoja del NCI, SQL Server tiene un RID para seguir directamente en lugar de tener que atravesar el índice agrupado.

¿Alguien sabe de pruebas formales similares que se han llevado a cabo en esta área y, de ser así, cuáles fueron los resultados?

Respuestas:


41

Para verificar su solicitud, creé 2 tablas siguiendo este esquema:

  • 7,9 millones de registros que representan información de saldo.
  • un campo de identidad que cuenta de 1 a 7.9 millones
  • un campo numérico que agrupa los registros en aproximadamente 500k grupos.

La primera tabla llamada heapobtuvo un índice no agrupado en el campo group. La segunda tabla llamada clustobtuvo un índice agrupado en el campo secuencial llamado keyy un índice no agrupado en el campogroup

Las pruebas se ejecutaron en un procesador I5 M540 con 2 núcleos hiperprocesados, memoria de 4 Gb y Windows 7 de 64 bits.

Microsoft SQL Server 2008 R2 (RTM) - 10.50.1600.1 (X64) 
Apr  2 2010 15:48:46 
Developer Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1)  

Actualización el 9 de marzo de 2011 : hice un segundo punto de referencia más extenso ejecutando el siguiente código .net y registrando la Duración, CPU, Lecturas, Escrituras y RowCounts en SQL Server Profiler. (El CommandText utilizado se mencionará en los resultados).

NOTA: CPU y duración se expresan en milisegundos

  • 1000 consultas
  • cero consultas de CPU se eliminan de los resultados
  • 0 filas afectadas se eliminan de los resultados
int[] idList = new int[] { 6816588, 7086702, 6498815 ... }; // 1000 values here.
using (var conn = new SqlConnection(@"Data Source=myserver;Initial Catalog=mydb;Integrated Security=SSPI;"))
            {
                conn.Open();
                using (var cmd = new SqlCommand())
                {
                    cmd.Connection = conn;
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = "select * from heap where common_key between @id and @id+1000"; 
                    cmd.Parameters.Add("@id", SqlDbType.Int);
                    cmd.Prepare();
                    foreach (int id in idList)
                    {
                        cmd.Parameters[0].Value = id;

                        using (var reader = cmd.ExecuteReader())
                        {
                            int count = 0;
                            while (reader.Read())
                            {
                                count++;
                            }
                            Console.WriteLine(String.Format("key: {0} => {1} rows", id, count));
                        }
                    }
                }
            }

Fin de la actualización el 9 de marzo de 2011 .

SELECCIONE el rendimiento

Para verificar los números de rendimiento, realicé las siguientes consultas una vez en la tabla de montón y otra en la tabla de clusters:

select * from heap/clust where group between 5678910 and 5679410
select * from heap/clust where group between 6234567 and 6234967
select * from heap/clust where group between 6455429 and 6455729
select * from heap/clust where group between 6655429 and 6655729
select * from heap/clust where group between 6955429 and 6955729
select * from heap/clust where group between 7195542 and 7155729

Los resultados de este punto de referencia son para heap:

rows  reads CPU   Elapsed 
----- ----- ----- --------
1503  1510  31ms  309ms
401   405   15ms  283ms
2700  2709  0ms   472ms
0     3     0ms   30ms
2953  2962  32ms  257ms
0     0     0ms   0ms

Actualización el 9 de marzo de 2011 : cmd.CommandText = "select * from heap where group between @id and @id+1000";

  • 721 filas tienen> 0 CPU y afectan a más de 0 filas
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    6368         -         
Cpu            15        374      37   0.00754
Reads        1069      91459    7682   1.20155
Writes          0          0       0   0.00000
Duration   0.3716   282.4850 10.3672   0.00180

Fin de la actualización el 9 de marzo de 2011 .


para la tabla clustlos resultados son:

rows  reads CPU   Elapsed 
----- ----- ----- --------
1503  4827  31ms  327ms
401   1241  0ms   242ms
2700  8372  0ms   410ms
0     3     0ms   0ms
2953  9060  47ms  213ms
0     0     0ms   0ms

Actualización el 9 de marzo de 2011 : cmd.CommandText = "select * from clust where group between @id and @id+1000";

  • 721 filas tienen> 0 CPU y afectan a más de 0 filas
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    6056         -
Cpu            15        468      38   0.00782
Reads        3194     227018   20457   3.37618
Writes          0          0       0       0.0
Duration   0.3949   159.6223 11.5699   0.00214

Fin de la actualización el 9 de marzo de 2011 .


SELECCIONAR CON UNIRSE

cmd.CommandText = "select * from heap/clust h join keys k on h.group = k.group where h.group between @id and @id+1000";


Los resultados de este punto de referencia son para heap:

873 filas tienen> 0 CPU y afectan a más de 0 filas

Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1009       4170    1683         -
Cpu            15         47      18   0.01175
Reads        2145       5518    2867   1.79246
Writes          0          0       0   0.00000
Duration   0.8215   131.9583  1.9095   0.00123

Los resultados de este punto de referencia son para clust:

865 filas tienen> 0 CPU y afectan a más de 0 filas

Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       4143    1685         -
Cpu            15         47      18   0.01193
Reads        5320      18690    8237   4.97813
Writes          0          0       0   0.00000
Duration   0.9699    20.3217  1.7934   0.00109

ACTUALIZAR el rendimiento

El segundo lote de consultas son declaraciones de actualización:

update heap/clust set amount = amount + 0 where group between 5678910 and 5679410
update heap/clust set amount = amount + 0 where group between 6234567 and 6234967
update heap/clust set amount = amount + 0 where group between 6455429 and 6455729
update heap/clust set amount = amount + 0 where group between 6655429 and 6655729
update heap/clust set amount = amount + 0 where group between 6955429 and 6955729
update heap/clust set amount = amount + 0 where group between 7195542 and 7155729

Los resultados de este punto de referencia para heap:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  3013  31ms  175ms
401   806   0ms   22ms
2700  5409  47ms  100ms
0     3     0ms   0ms
2953  5915  31ms  88ms
0     0     0ms   0ms

Actualización el 9 de marzo de 2011 : cmd.CommandText = "update heap set amount = amount + @id where group between @id and @id+1000";

  • 811 filas tienen> 0 CPU y afectan a más de 0 filas
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    5598       811         
Cpu            15        873      56   0.01199
Reads        2080     167593   11809   2.11217
Writes          0       1687     121   0.02170
Duration   0.6705   514.5347 17.2041   0.00344

Fin de la actualización el 9 de marzo de 2011 .


Los resultados de este punto de referencia para clust:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  9126  16ms  35ms
401   2444  0ms   4ms
2700  16385 31ms  54ms
0     3     0ms   0ms 
2953  17919 31ms  35ms
0     0     0ms   0ms

Actualización el 9 de marzo de 2011 : cmd.CommandText = "update clust set amount = amount + @id where group between @id and @id+1000";

  • 853 filas tienen> 0 CPU y afectan a más de 0 filas
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    5420         -
Cpu            15        594      50   0.01073
Reads        6226     432237   33597   6.20450
Writes          0       1730     110   0.01971
Duration   0.9134   193.7685  8.2919   0.00155

Fin de la actualización el 9 de marzo de 2011 .


BORRAR puntos de referencia

el tercer lote de consultas que ejecuté son declaraciones de eliminación

delete heap/clust where group between 5678910 and 5679410
delete heap/clust where group between 6234567 and 6234967
delete heap/clust where group between 6455429 and 6455729
delete heap/clust where group between 6655429 and 6655729
delete heap/clust where group between 6955429 and 6955729
delete heap/clust where group between 7195542 and 7155729

El resultado de este punto de referencia para heap:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  10630 62ms  179ms
401   2838  0ms   26ms
2700  19077 47ms  87ms
0     4     0ms   0ms
2953  20865 62ms  196ms
0     4     0ms   9ms

Actualización el 9 de marzo de 2011 : cmd.CommandText = "delete heap where group between @id and @id+1000";

  • 724 filas tienen> 0 CPU y afectan a más de 0 filas
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts     192      69788    4781         -
Cpu            15        499      45   0.01247
Reads         841     307958   20987   4.37880
Writes          2       1819     127   0.02648
Duration   0.3775  1534.3383 17.2412   0.00349

Fin de la actualización el 9 de marzo de 2011 .


El resultado de este punto de referencia para clust:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  9228  16ms  55ms
401   3681  0ms   50ms
2700  24644 46ms  79ms
0     3     0ms   0ms
2953  26955 47ms  92ms
0     3     0ms   0ms

Actualización el 9 de marzo de 2011 :

cmd.CommandText = "delete clust where group between @id and @id+1000";

  • 751 filas tienen> 0 CPU y afectan a más de 0 filas
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts     144      69788    4648         -
Cpu            15        764      56   0.01538
Reads         989     458467   30207   6.48490
Writes          2       1830     127   0.02694
Duration   0.2938  2512.1968 24.3714   0.00555

Fin de la actualización el 9 de marzo de 2011 .


INSERTAR puntos de referencia

La última parte del punto de referencia es la ejecución de declaraciones de inserción.

insertar en el montón / clust (...) valores (...), (...), (...), (...), (...), (...)


El resultado de este punto de referencia para heap:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
6     38    0ms   31ms

Actualización el 9 de marzo de 2011 :

string str = @"insert into heap (group, currency, year, period, domain_id, mtdAmount, mtdAmount, ytdAmount, amount, ytd_restated, restated, auditDate, auditUser)
                    values";

                    for (int x = 0; x < 999; x++)
                    {
                        str += string.Format(@"(@id + {0}, 'EUR', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test'),  ", x);
                    }
                    str += string.Format(@"(@id, 'CAD', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test') ", 1000);

                    cmd.CommandText = str;
  • 912 declaraciones tienen> 0 CPU
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       1000    1000         -
Cpu            15       2138      25   0.02500
Reads        5212       7069    6328   6.32837
Writes         16         34      22   0.02222
Duration   1.6336   293.2132  4.4009   0.00440

Fin de la actualización el 9 de marzo de 2011 .


El resultado de este punto de referencia para clust:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
6     50    0ms   18ms

Actualización el 9 de marzo de 2011 :

string str = @"insert into clust (group, currency, year, period, domain_id, mtdAmount, mtdAmount, ytdAmount, amount, ytd_restated, restated, auditDate, auditUser)
                    values";

                    for (int x = 0; x < 999; x++)
                    {
                        str += string.Format(@"(@id + {0}, 'EUR', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test'),  ", x);
                    }
                    str += string.Format(@"(@id, 'CAD', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test') ", 1000);

                    cmd.CommandText = str;
  • 946 declaraciones tienen> 0 CPU
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       1000    1000         -      
Cpu            15       2403      21   0.02157
Reads        6810       8997    8412   8.41223
Writes         16         25      19   0.01942
Duration   1.5375   268.2571  6.1463   0.00614

Fin de la actualización el 9 de marzo de 2011 .


Conclusiones

Aunque hay más lecturas lógicas al acceder a la tabla con el índice agrupado y no agrupado (mientras se utiliza el índice no agrupado) los resultados de rendimiento son:

  • Las declaraciones SELECT son comparables
  • Las declaraciones de ACTUALIZACIÓN son más rápidas con un índice agrupado en su lugar
  • Las declaraciones DELETE son más rápidas con un índice agrupado en su lugar
  • Las instrucciones INSERT son más rápidas con un índice agrupado en su lugar

Por supuesto, mi punto de referencia estaba muy limitado en un tipo específico de tabla y con un conjunto muy limitado de consultas, pero creo que, en base a esta información, ya podemos comenzar a decir que prácticamente siempre es mejor crear un índice agrupado en su tabla.

Actualización el 9 de marzo de 2011 :

Como podemos ver en los resultados agregados, las conclusiones sobre las pruebas limitadas no fueron correctas en todos los casos.

Duración ponderada

Los resultados ahora indican que las únicas declaraciones que se benefician del índice agrupado son las declaraciones de actualización. Las otras declaraciones son aproximadamente un 30% más lentas en la tabla con índice agrupado.

Algunos gráficos adicionales en los que tracé la duración ponderada por consulta para montón vs clúster. Montón de duración ponderada vs agrupados para Seleccionar

Montón de duración ponderada vs agrupado para unirse

Montón de duración ponderada frente a agrupamiento para actualización

Montón de duración ponderada vs agrupado para Eliminar

Como puede ver, el perfil de rendimiento para las instrucciones de inserción es bastante interesante. Los picos son causados ​​por algunos puntos de datos que tardan mucho más en completarse. Montón de duración ponderada frente a agrupamiento para inserción

Fin de la actualización el 9 de marzo de 2011 .


@ Martin Intentaré ejecutar esto en un servidor con algunas tablas con 500 millones de registros cuando encuentre algo de tiempo la próxima semana.
Filip De Vos

Dudo de la veracidad de esta prueba. Algunas partes necesitan mucha atención, como el rendimiento de INSERT que afirma que el índice agrupado es más rápido: hubo más lecturas en la versión CLUST, pero el tiempo transcurrido es menor. Personalmente, habría ignorado el tiempo transcurrido dentro de los 10 segundos de milisegundos (variabilidad de tiempo); significa menos que el recuento de lectura.

Confirmar de Kimberly Tripp El índice agrupado debate continúa en la que explica por qué la mayoría (si no todos) con una mesa de operaciones en clúster son más rápidos que con un montón - alguna contraria a los resultados ...
marc_s

1
@Martin, @Richard, @marc_s. Estoy trabajando en un punto de referencia más serio en este momento. Espero poder agregar los resultados más tarde hoy.
Filip De Vos

1
@Filip - ¡Guau! Definitivamente mereces la recompensa por todo el trabajo duro que has puesto en esta respuesta. Aunque, como acertadamente señala, este fue un punto de referencia en un tipo específico de tabla con un conjunto muy limitado de consultas y el kilometraje, sin duda, variará.
Martin Smith

12

Como Kimberly Tripp, la reina de la indexación, explica muy bien en su publicación de blog, The Clustered Index Debate continúa ... , tener una clave de agrupación en una tabla de base de datos acelera bastante todas las operaciones, no solo SELECT.

SELECT son generalmente más lentos en un montón en comparación con una tabla agrupada, siempre que elija una buena clave de agrupación, algo así como un INT IDENTITY. Si usa una clave de agrupación realmente muy mala, como un GUID o una clave compuesta con muchos componentes de longitud variable, entonces, pero solo entonces, un montón podría ser más rápido. Pero en ese caso, realmente necesita limpiar el diseño de su base de datos en primer lugar ...

Entonces, en general, no creo que haya ningún punto en un montón: elija una clave de agrupación buena y útil y debería beneficiarse en todos los aspectos.


3
Esta es una no respuesta. Martin es bastante sólido en SQL Server; la pregunta estaba destinada a obtener resultados verificados probados en el mundo real de las pruebas de rendimiento, no más teoría.

El artículo de Kimberly Tripp vinculado efectivamente supone que todos los índices no agrupados están cubriendo. Si ese es el caso, entonces no habría búsquedas, y la ventaja del montón en las búsquedas sería negada. Sin embargo, ese no es un mundo en el que la mayoría de nosotros vivimos. En nuestros casos, tratar de diseñar todos o la mayoría de nuestros índices no agrupados para cubrirlos crea sus propios problemas.

@ dbaguy52: ¿por qué crees que Kim Tripp supone que todos los índices de NC están cubriendo? No veo ninguna noción de que en su entrada de blog ..... por favor explicar con más detalle lo que hace que se cree que es el caso (o eso es su asunción)
marc_s

7

Acabo de encontrar este artículo de Joe Chang que aborda esta pregunta. Pegó sus conclusiones a continuación.

Considere una tabla para la cual los índices tienen profundidad 4, de modo que haya un nivel de raíz, 2 niveles intermedios y el nivel de hoja. La búsqueda de índice para una sola clave de índice (es decir, sin búsqueda de clave) generaría 4 IO lógicas (LIO). Ahora considere si se requiere una búsqueda de clave. Si la tabla tiene un índice agrupado también de profundidad 4, cada búsqueda de clave genera 4 LIO. Si la tabla fuera un montón, cada búsqueda de clave genera 1 LIO. En realidad, la búsqueda de claves para un montón es aproximadamente un 20-30% menos costosa que una búsqueda de claves para un índice agrupado, no cerca de la relación LIO 4: 1.


1
Lo interesante a tener en cuenta es que la cita de Joe Chang identificó una ventaja de eficiencia del 20-30% para los montones basada en sus suposiciones, que es más o menos la misma ventaja identificada en la actualización del artículo del 9 de marzo.
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.