Índice não clusterizado é mais rápido que o índice clusterizado?

9

Ambas as tabelas têm a mesma estrutura e 19972 linhas em cada tabela. para praticar a indexação, criei as duas tabelas com a mesma estrutura e criei

clustered index on persontb(BusinessEntityID)

e

nonclustered index on Persontb_NC(BusinessEntityId)

e estrutura da 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

insira a descrição da imagem aqui

Por que o índice clusterizado leva 62% e 38% não clusterizado?


fonte
11
Por que votar em fechar?

Respostas:

10

Sim, o índice clusterizado possui menos linhas por página que o índice não clusterizado, pois as páginas folha do índice clusterizado devem armazenar os valores para as outras duas colunas ( FirstNamee LastName).

As páginas folha da NCI armazenam apenas os BusinessEntityIdvalores e um localizador de linhas (RID, se a tabela for uma pilha ou a chave do IC).

Portanto, os custos estimados refletem o maior número de leituras e os requisitos de IO.

Se você declarar o NCI como

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

então seria semelhante ao índice em cluster.

Martin Smith
fonte
5

O índice clusterizado contém não apenas dados do índice da coluna ativados, mas também dados de todas as outras colunas. (Só pode haver um índice em cluster por tabela)

O índice não clusterizado contém apenas dados da (s) coluna (s) indexada (s) e um ponteiro row_id para onde estão os demais dados.

Portanto, esse índice não clusterizado específico é mais leve e menos leitura é necessária para varrer / procurar por ele, e essa consulta específica funcionará mais rapidamente.

No entanto, se você tentou recuperar o Nome e o Sobrenome, seria diferente e o índice em cluster deveria ter um desempenho melhor.

Nenad Zivkovic
fonte
2

As porcentagens entre os planos de consulta não fazem sentido em comparação. Você deve comparar as consultas para ter uma comparação válida. Além disso, pequenas contagens de linhas tendem a ocultar diferenças de desempenho entre estratégias de indexação. Ao aumentar a contagem de linhas para 10 milhões, você pode obter uma imagem mais clara das diferenças de desempenho.

Há um exemplo de script que cria 3 tabelas, as duas acima e uma terceira com um índice em cluster e não em cluster.

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)

Preencher as tabelas com 10 milhões de linhas

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 o tamanho no disco dos í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 

E os 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

O índice clusterizado do T1 tem cerca de 1,6 GB de tamanho. O índice não agrupado do T2 é de 170 MB (economia de 90% no IO). O índice não agrupado do T3 é de 97 MB, ou cerca de 95% menos IO que o T1.

Portanto, com base nas E / S exigidas, o plano de consulta original deveria ter sido mais próximo das linhas de 10% / 90%, não de 38% / 62%. Além disso, como é provável que o índice não agrupado se encaixe inteiramente na memória, a diferença pode ser maior ainda, pois as E / S do disco são muito caras.

StrayCatDBA
fonte
11
É um salto para inferir que sua 10%/90%figura é mais precisa que a 38%/62%. Seqüências de caracteres com comprimento entre 100 e 200 certamente serão uma superestimação bruta dos requisitos de espaço para um par de nome / sobrenome, para que você tenha uma densidade de página menor que o OP. Quando tento com seus dados de exemplo, os custos estimados aparecem em 87% / 13% .
Martin Smith
11
O SQL Server aleady referem-se ao data_pagesno sys.allocation_units. Você pode ver isso a partir CREATE TABLE T1(C INT);CREATE TABLE T2(C INT);UPDATE STATISTICS T1 WITH PAGECOUNT = 1;UPDATE STATISTICS T2 WITH PAGECOUNT = 100seguida, comparando os custos estimadosSELECT * FROM T1;SELECT * FROM T2;
Martin Smith
Releia a primeira frase da minha resposta. Comparar custos diretamente não faz sentido. Para a diferença de desempenho entre as consultas do OP, uma melhor estimativa pode ser obtida empiricamente, calculando a redução no tamanho dos índices (e, portanto, no número de pedidos de veiculação), não nos custos do otimizador.
StrayCatDBA
11
De um modo geral, é sim, mas, neste caso, a razão pela qual o otimizador de consulta custa o índice em cluster como mais do que o índice não em cluster (o assunto desta pergunta) é precisamente devido às diferentes contagens de páginas.
Martin Smith
11
De acordo com http://www.qdpma.com/ppt/CostFormulas2.ppt A fórmula utilizada para um custo Index Scan ou índice atingir sem pesquisa é IO (dependente versão) (0,003125 + 0,00074074 por página) e CPU (0,0001581 0,0000011 + por linha). Os custos e linhas fixos são iguais para IC e NCI, portanto, a única variável são as páginas.
Martin Smith