Desempenho de índices não agrupados em pilhas versus índices agrupados

39

Este white paper de 2007 compara o desempenho de instruções individuais de seleção / inserção / exclusão / atualização e seleção de intervalo em uma tabela organizada como um índice clusterizado versus o de uma tabela organizada como um heap com um índice não clusterizado nas mesmas colunas-chave do IC tabela.

Geralmente, a opção de índice clusterizado teve um desempenho melhor nos testes, pois há apenas uma estrutura a ser mantida e porque não há necessidade de pesquisas de indicadores.

Um caso potencialmente interessante não coberto pelo documento seria uma comparação entre um índice não clusterizado em um heap e um índice não clusterizado em um índice clusterizado. Nesse caso, eu esperava que o heap pudesse ter um desempenho melhor, pois uma vez no nível folha da NCI, o SQL Server tem um RID para seguir diretamente, em vez de precisar percorrer o índice em cluster.

Alguém está ciente de testes formais semelhantes que foram realizados nesta área e, em caso afirmativo, quais foram os resultados?

Martin Smith
fonte

Respostas:

41

Para verificar sua solicitação, criei 2 tabelas seguindo este esquema:

  • 7,9 milhões de registros representando informações de saldo.
  • um campo de identidade que conta de 1 a 7,9 milhões
  • um campo numérico agrupando os registros em cerca de 500 mil grupos.

A primeira tabela chamada heapobteve um índice não clusterizado no campo group. A segunda tabela chamada clustobteve um índice agrupado no campo seqüencial chamado keye um índice não clusterizado no campogroup

Os testes foram executados em um processador I5 M540 com 2 núcleos hyperthread, memória 4Gb e janelas de 64 bits 7.

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)  

Atualização em 9 de março de 2011 : fiz um segundo benchmark mais extenso executando o seguinte código .net e duração do log, CPU, leituras, gravações e RowCounts no Sql Server Profiler. (O CommandText usado será mencionado nos resultados.)

NOTA: CPU e duração são expressos em milissegundos

  • 1000 consultas
  • zero consultas de CPU são eliminadas dos resultados
  • 0 linhas afetadas são eliminadas dos 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));
                        }
                    }
                }
            }

Fim da atualização em 9 de março de 2011 .

SELECIONAR desempenho

Para verificar os números de desempenho, executei as seguintes consultas uma vez na tabela heap e outra na tabela clust:

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

Os resultados deste benchmark são 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

Atualização em 9 de março de 2011 : cmd.CommandText = "select * from heap where group between @id and @id+1000";

  • 721 linhas têm> 0 CPU e afetam mais de 0 linhas
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

Fim da atualização em 9 de março de 2011 .


para a tabela, clustos resultados são:

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

Atualização em 9 de março de 2011 : cmd.CommandText = "select * from clust where group between @id and @id+1000";

  • 721 linhas têm> 0 CPU e afetam mais de 0 linhas
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

Fim da atualização em 9 de março de 2011 .


Desempenho SELECT WITH JOIN

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


Os resultados deste benchmark são para heap:

873 linhas têm> 0 CPU e afetam mais de 0 linhas

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

Os resultados deste benchmark são para clust:

865 linhas têm> 0 CPU e afetam mais de 0 linhas

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

UPDATE performance

O segundo lote de consultas são instruções de atualização:

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

os resultados desta referência 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

Atualização em 9 de março de 2011 : cmd.CommandText = "update heap set amount = amount + @id where group between @id and @id+1000";

  • 811 linhas têm> 0 CPU e afetam mais de 0 linhas
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

Fim da atualização em 9 de março de 2011 .


os resultados desta referência 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

Atualização em 9 de março de 2011 : cmd.CommandText = "update clust set amount = amount + @id where group between @id and @id+1000";

  • As linhas 853 têm> 0 CPU e afetam mais de 0 linhas
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

Fim da atualização em 9 de março de 2011 .


DELETE benchmarks

o terceiro lote de consultas que executei são instruções de exclusão

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

O resultado deste benchmark 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

Atualização em 9 de março de 2011 : cmd.CommandText = "delete heap where group between @id and @id+1000";

  • 724 linhas têm> 0 CPU e afetam mais de 0 linhas
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

Fim da atualização em 9 de março de 2011 .


o resultado desta referência 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

Atualização em 9 de março de 2011 :

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

  • 751 linhas têm> 0 CPU e afetam mais de 0 linhas
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

Fim da atualização em 9 de março de 2011 .


INSERT benchmarks

A última parte do benchmark é a execução de instruções de inserção.

inserir em heap / clust (...) valores (...), (...), (...), (...), (...), (...)


O resultado deste benchmark para heap:

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

Atualização em 9 de março 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 instruções têm> 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

Fim da atualização em 9 de março de 2011 .


O resultado deste benchmark para clust:

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

Atualização em 9 de março 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 instruções têm> 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

Fim da atualização em 9 de março de 2011 .


Conclusões

Embora haja mais leituras lógicas em andamento ao acessar a tabela com o índice clusterizado e não clusterizado (enquanto estiver usando o índice não clusterizado), os resultados de desempenho são:

  • Instruções SELECT são comparáveis
  • Instruções UPDATE são mais rápidas com um índice clusterizado
  • Instruções DELETE são mais rápidas com um índice clusterizado
  • Instruções INSERT são mais rápidas com um índice clusterizado

É claro que meu benchmark era muito limitado em um tipo específico de tabela e com um conjunto muito limitado de consultas, mas acho que, com base nessas informações, já podemos começar a dizer que é praticamente sempre melhor criar um índice agrupado em sua tabela.

Atualização em 9 de março de 2011 :

Como podemos ver pelos resultados adicionados, as conclusões dos testes limitados não eram corretas em todos os casos.

Duração Ponderada

Os resultados agora indicam que as únicas instruções que se beneficiam do índice clusterizado são as instruções de atualização. As outras instruções são cerca de 30% mais lentas na tabela com índice agrupado.

Alguns gráficos adicionais em que plotei a duração ponderada por consulta para heap vs clust. Heap de duração ponderada vs clusterizado para Select

Heap de duração ponderada vs clusterizado para associação

Heap de duração ponderada vs clusterizado para atualização

Heap de duração ponderada vs clusterizado para Excluir

Como você pode ver, o perfil de desempenho para as instruções de inserção é bastante interessante. Os picos são causados ​​por alguns pontos de dados que levam muito mais tempo para serem concluídos. Heap de duração ponderada vs clusterizado para Insert

Fim da atualização em 9 de março de 2011 .

Filip De Vos
fonte
@ Martin Vou tentar executar isso em um servidor com algumas tabelas com 500 milhões de registros quando eu encontrar algum tempo na próxima semana.
Filip De Vos
Duvido da veracidade deste teste. Algumas partes precisam de muita atenção, como o desempenho do INSERT, alegando que o índice em cluster é mais rápido - houve mais leituras na versão CLUST, mas o tempo decorrido é menor. Pessoalmente, eu teria ignorado o tempo decorrido dentro de 10s de milissegundos (variabilidade de tempo) - isso significa menos do que a contagem de leitura.
Confira de Kimberly Tripp O Clustered Index debate continua onde ela explica por que a maioria (se não todas) as operações com uma tabela em cluster são mais rápidos do que com um montão - alguns contrária a seus resultados ...
marc_s
11
@Martin, @Richard, @marc_s. Estou trabalhando em um benchmark mais sério agora. Espero poder adicionar os resultados ainda hoje.
Filip De Vos
11
@Filip - Uau! Você definitivamente merece a recompensa por todo o trabalho duro que dedicou a esta resposta. Embora, como você bem indique, essa foi uma referência em um tipo específico de tabela com um conjunto muito limitado de consultas e a quilometragem variará sem dúvida.
27611 Martin Martin Smith
12

Como Kimberly Tripp - a rainha da indexação - explica muito bem em seu blog The Clustered Index Debate continua ... , ter uma chave de cluster em uma tabela de banco de dados praticamente acelera todas as operações - não apenas SELECT.

SELECT geralmente são mais lentos em um heap em comparação com uma tabela em cluster, desde que você escolha uma boa chave de cluster - algo como um INT IDENTITY. Se você usar uma chave de cluster realmente muito ruim, como um GUID ou uma chave composta com muitos componentes de comprimento variável, então, mas somente então, um heap poderá ser mais rápido. Mas, nesse caso, você realmente precisa limpar o design do banco de dados em primeiro lugar ...

Portanto, em geral, acho que não há nenhum ponto em um monte - escolha uma chave de cluster boa e útil e você deve se beneficiar em todos os aspectos.

marc_s
fonte
3
Esta é uma não resposta. Martin é bastante sólido no SQL Server; a pergunta pretendia obter resultados verificados testados no mundo real a partir de testes de desempenho, e não mais teoria.
O artigo de Kimberly Tripp vinculado efetivamente assume que todos os índices não clusterizados estão cobrindo. Se for esse o caso, não haverá pesquisas e a vantagem da pilha nas pesquisas será negada. Mas esse não é um mundo em que a maioria de nós vive. Em nossos casos, tentar projetar todos ou a maioria de nossos índices não clusterizados para cobrir cria problemas próprios.
@ dbaguy52: por que você acha que Kim Tripp assume que todos os índices NC estão cobrindo? Não vejo qualquer noção de que, em sua postagem no blog ..... por favor, explicar mais detalhadamente o que faz você acreditar que é o caso (ou é ela suposição)
marc_s
7

Aconteceu de encontrar este artigo de Joe Chang que trata dessa questão. Colou suas conclusões abaixo.

Considere uma tabela para a qual os índices têm profundidade 4, para que haja um nível de raiz, 2 níveis intermediários e o nível de folha. A busca de índice por uma única chave de índice (ou seja, sem pesquisa de chave) geraria 4 IO lógicas (LIO). Agora considere se uma pesquisa de chave é necessária. Se a tabela tiver um índice clusterizado também com profundidade 4, cada pesquisa de chave gerará 4 LIO. Se a tabela fosse uma pilha, cada pesquisa de chave geraria 1 LIO. Na verdade, a pesquisa de chave em um heap é cerca de 20 a 30% mais barata que a pesquisa de chave em um índice clusterizado, nem em qualquer lugar próximo da proporção 4: 1 LIO.

Martin Smith
fonte
11
O interessante é notar que a citação de Joe Chang identificou uma vantagem de eficiência de 20 a 30% para os montões com base em suas suposições, que é praticamente a mesma vantagem identificada na atualização de 9 de março do artigo.