L'indice non cluster è più veloce dell'indice cluster?


9

Entrambe le tabelle hanno la stessa struttura e 19972 righe in ciascuna tabella. per esercitarmi nell'indicizzazione, ho creato entrambe le tabelle con la stessa struttura e create

clustered index on persontb(BusinessEntityID)

e

nonclustered index on Persontb_NC(BusinessEntityId)

e struttura del tavolo

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

inserisci qui la descrizione dell'immagine

Perché l'indice cluster prende il 62% e il 38% non cluster?


1
Perché votare per chiudere?

Risposte:


10

Sì, l'indice cluster ha meno righe per pagina rispetto all'indice non cluster poiché le pagine foglia dell'indice cluster devono memorizzare i valori per le altre due colonne ( FirstNamee LastName).

Le pagine foglia dell'NCI memorizzano solo i BusinessEntityIdvalori e un posizionatore di riga (RID se la tabella è un heap o la chiave CI in caso contrario).

Quindi i costi stimati riflettono il maggior numero di letture e requisiti di I / O.

Se dovessi dichiarare l'NCI come

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

quindi sarebbe simile all'indice cluster.


5

L'indice cluster non contiene solo i dati dell'indice di colonna, ma anche i dati di tutte le altre colonne. (Può esserci un solo indice cluster per tabella)

L'indice non cluster contiene solo i dati delle colonne indicizzate e un puntatore row_id su dove si trova il resto dei dati.

Pertanto questo particolare indice non cluster è più leggero e sono necessarie meno letture per scansionarlo / cercarlo e questa particolare query funzionerà più velocemente.

Tuttavia, hai provato a recuperare anche FirstName e LastName, sarebbe diverso e l'indice cluster dovrebbe funzionare meglio.


2

Le percentuali tra i piani di query non hanno senso per il confronto definitivo. È necessario confrontare le query per avere un confronto valido. Inoltre, i conteggi di piccole righe hanno la tendenza a nascondere le differenze di prestazioni tra le strategie di indicizzazione. Aumentando il conteggio delle righe a 10 milioni è possibile ottenere un quadro più chiaro delle differenze di prestazioni.

Esiste uno script di esempio che crea 3 tabelle, le tue due dall'alto e una terza con un indice sia cluster che non 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)

Popolare le tabelle con 10 milioni di righe

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'

Possiamo usare sys.dm_db_index_physical_stats per vedere la dimensione sul disco degli indici.

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 i risultati:

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

L'indice cluster di T1 ha una dimensione di circa 1,6 GB. L'indice non cluster di T2 è di 170 MB (risparmio del 90% in I / O). L'indice non cluster di T3 è 97 MB, ovvero circa il 95% in meno di IO rispetto a T1.

Pertanto, in base all'IO richiesto, il piano di query originale avrebbe dovuto essere più lungo il 10% / 90%, non il 38% / 62%. Inoltre, poiché è probabile che l'indice non cluster si adatti interamente alla memoria, la differenza potrebbe essere ancora maggiore, poiché l'IO del disco è molto costoso.


1
È un po 'un salto per dedurre che la tua 10%/90%figura è più accurata di 38%/62%. Le stringhe con una lunghezza compresa tra 100 e 200 costituiranno sicuramente una sovrastima grossolana dei requisiti di spazio per una coppia nome / cognome, quindi avrai una densità di pagina inferiore rispetto al PO. Quando provo contro i tuoi dati di esempio, i costi stimati si presentano come 87% / 13% .
Martin Smith,

1
SQL Server fa già riferimento a data_pagesin sys.allocation_units. Puoi vederlo da CREATE TABLE T1(C INT);CREATE TABLE T2(C INT);UPDATE STATISTICS T1 WITH PAGECOUNT = 1;UPDATE STATISTICS T2 WITH PAGECOUNT = 100allora confrontando i costi stimatiSELECT * FROM T1;SELECT * FROM T2;
Martin Smith,

Rileggi la prima frase nella mia risposta. Il confronto diretto dei costi non ha senso. Per la differenza di prestazione tra le query del PO, una stima migliore può essere derivata empiricamente calcolando la riduzione della dimensione degli indici (e quindi il numero di IO), non dai costi dell'ottimizzatore.
StrayCatDBA,

1
In generale è sì, ma in questo caso il motivo per cui Query Optimizer costa all'indice cluster più dell'indice non cluster (oggetto di questa domanda) è proprio a causa dei diversi conteggi delle pagine.
Martin Smith,

1
Secondo http://www.qdpma.com/ppt/CostFormulas2.ppt La formula utilizzata per costare un indice di scansione o indice di ricerca senza ricerca è (versione dipendente) IO (0,003,125 mila + 0,00,074074 millions per pagina) e CPU (0,0001,581 mila + ,0000,011 mila per riga). I costi fissi e le righe sono uguali per CI e NCI, quindi l'unica variabile sono le pagine.
Martin Smith,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.