Indeks nonclustered lebih cepat dari indeks cluster?

9

Kedua tabel memiliki struktur yang sama dan baris 19972 di setiap tabel. untuk mempraktekkan pengindeksan, saya membuat kedua tabel memiliki struktur yang sama dan dibuat

clustered index on persontb(BusinessEntityID)

dan

nonclustered index on Persontb_NC(BusinessEntityId)

dan struktur tabel

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

masukkan deskripsi gambar di sini

Mengapa indeks clustered membutuhkan 62% dan non clustered 38%?


sumber
1
Mengapa memilih yang dekat?

Jawaban:

10

Ya indeks berkerumun memiliki baris lebih sedikit per halaman daripada indeks nonkluster karena halaman daun dari indeks berkerumun harus menyimpan nilai untuk dua kolom lainnya ( FirstNamedan LastName).

Halaman daun NCI hanya menyimpan BusinessEntityIdnilai dan pelacak baris (RID jika tabelnya adalah tumpukan atau kunci CI sebaliknya).

Jadi perkiraan biaya mencerminkan jumlah pembacaan dan persyaratan IO yang lebih besar.

Jika Anda menyatakan NCI sebagai

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

maka itu akan mirip dengan indeks berkerumun.

Martin Smith
sumber
5

Indeks Clustered tidak hanya berisi data dari indeks kolom aktif, tetapi juga data dari semua kolom lainnya. (Hanya ada satu indeks berkerumun per tabel)

Nonclustered index hanya berisi data dari kolom yang diindeks, dan sebuah pointer row_id ke tempat data lainnya berada.

Oleh karena itu indeks nonclustered khusus ini lebih ringan dan lebih sedikit membaca diperlukan untuk memindai / mencari melalui itu dan permintaan khusus ini akan bekerja lebih cepat.

Namun, sudahkah Anda mencoba untuk mengambil FirstName dan LastName juga, itu akan berbeda dan indeks cluster harus berkinerja lebih baik.

Nenad Zivkovic
sumber
2

Persentase antara rencana kueri tidak ada artinya untuk dibandingkan secara langsung. Anda harus membandingkan permintaan untuk memiliki perbandingan yang valid. Selain itu, jumlah baris kecil memiliki kecenderungan untuk menyembunyikan perbedaan kinerja antara strategi pengindeksan. Dengan meningkatkan jumlah baris menjadi 10 juta, Anda dapat memperoleh gambaran yang lebih jelas tentang perbedaan kinerja.

Ada skrip sampel yang membuat 3 tabel, dua Anda dari atas, dan yang ketiga dengan indeks berkerumun dan tidak berkerumun.

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)

Isi tabel dengan 10 juta baris

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'

Kita dapat menggunakan sys.dm_db_index_physical_stats untuk melihat ukuran pada disk indeks.

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 

Dan hasilnya:

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

Indeks clustered T1 adalah sekitar 1,6 GB. Indeks non-clustered T2 adalah 170 MB (penghematan 90% dalam IO). Indeks non-cluster T3 adalah 97 MB, atau sekitar 95% lebih sedikit IO daripada T1.

Jadi, berdasarkan dari IO yang diperlukan, rencana kueri asli seharusnya lebih sesuai 10% / 90%, bukan 38% / 62%. Juga, karena indeks non-cluster cenderung cocok sepenuhnya dalam memori, perbedaannya mungkin lebih besar lagi, karena disk IO sangat mahal.

StrayCatDBA
sumber
1
Ini sedikit lompatan untuk menyimpulkan bahwa 10%/90%sosok Anda lebih akurat daripada 38%/62%. String dengan panjang antara 100 dan 200 tentu akan menjadi perkiraan ruang terlalu tinggi untuk pasangan nama depan / nama belakang sehingga Anda akan memiliki kepadatan halaman yang lebih rendah daripada OP. Ketika saya mencoba terhadap data contoh Anda, perkiraan biaya muncul 87% / 13% .
Martin Smith
1
SQL Server tidak aleady mengacu pada data_pagesdi sys.allocation_units. Anda dapat melihat ini dari CREATE TABLE T1(C INT);CREATE TABLE T2(C INT);UPDATE STATISTICS T1 WITH PAGECOUNT = 1;UPDATE STATISTICS T2 WITH PAGECOUNT = 100kemudian membandingkan perkiraan biayaSELECT * FROM T1;SELECT * FROM T2;
Martin Smith
Harap baca kembali kalimat pertama dalam jawaban saya. Membandingkan biaya secara langsung tidak ada artinya. Untuk perbedaan kinerja antara kueri OP, estimasi yang lebih baik dapat diturunkan secara empiris dengan menghitung pengurangan ukuran indeks (dan karenanya jumlah IO), bukan oleh biaya dari pengoptimal.
StrayCatDBA
1
Secara umum itu ya, tetapi dalam hal ini alasan mengapa pengoptimal permintaan biaya indeks berkerumun lebih dari indeks non berkerumun (subjek pertanyaan ini) justru karena jumlah halaman yang berbeda.
Martin Smith
1
Menurut http://www.qdpma.com/ppt/CostFormulas2.ppt Rumus yang digunakan untuk biaya Index Scan atau Indeks Seek tanpa lookup (tergantung versi) IO (0,003125 + 0,00074074 per halaman) dan CPU (0,0001581 + 0,0000011 per baris). Biaya dan baris tetap sama untuk CI dan NCI sehingga satu-satunya variabel adalah halaman.
Martin Smith