W SQL Server wyróżniamy dwa podstawowe typy indeksów: klastrowy (clustered) i nieklastrowy (nonclustered), nazywanymi również odpowiednio grupującym i niegrupującym. Dlaczego używanie pierwszego z nich jest takie ważne?
Odpowiedź jest bardzo prosta. W przypadku gdy nie posiadamy indeksu klastrowego dane w tabeli zapisywane są kolejno na stronach danych (HEAP). Konsekwencją tego jest to, że wyświetlając interesujące nas dane musimy przeskanować całą tabelę.
Tworząc indeks klastrowy pozbywamy się tego problemu. W tym przypadku zamiast skanować całą tabelę – przechodzimy przez strukturę b-drzewa dzięki czemu w pamięci ląduje mniej stron danych, a same dane są układane zgodnie z tym co jest w indeksie.
Zalecenia do tworzenia indeksu tego typu są bardzo proste. Wybieramy jak najszybszy typ danych, najlepiej jedną kolumnę i na jej podstawie organizujemy ten indeks. Kryterium te spełnia idealnie klucz podstawowy (indeks ten jest domyślnie zakładany wraz z kluczem podstawowym). Należy pamiętać, że mimo to, że indeks ten układa wg danej kolumny dane na stronach danych, to indeks ten należy regularnie konserwować.
Poniższe doświadczenie w świetny sposób ilustruje jak dzięki indeksowi klastrowemu założonemu w naszym przypadku domyślnie z kluczem podstawowym pewne operacje (w naszym przypadku UPDATE) przebiegają szybciej.
Bez indeksu klastrowego:
USE tempdb;
GO
--Create a table as a heap
CREATE TABLE dbo.TestDB
( ID int IDENTITY(1,1) NOT NULL,
TestColumn1 datetime2 NOT NULL,
TestColumn2 nvarchar(100) NOT NULL,
TestColumn3 int NOT NULL
);
GO
-- Query sys.indexes to view the structure
SELECT * FROM sys.indexes WHERE OBJECT_NAME(object_id) = N'TestDB';
GO
-- Insert some data into the table
SET NOCOUNT ON;
DECLARE @Counter int = 0;
WHILE @Counter < 10000 BEGIN
INSERT dbo.TestDB (TestColumn1, TestColumn2, TestColumn3)
VALUES(SYSDATETIME(),'999-9999',CAST(RAND() * 1000 AS int));
SET @Counter += 1;
END;
GO
-- Modify the data in the table
SET NOCOUNT ON;
DECLARE @Counter int = 0;
WHILE @Counter < 10000 BEGIN
UPDATE dbo.TestDB SET TestColumn2 = REPLICATE('9',CAST(RAND() * 100 AS int))
WHERE ID = @Counter;
IF @Counter % 100 = 0 PRINT @Counter;
SET @Counter += 1;
END;
GO
--CLEANING
DROP TABLE dbo.TestDB;
GO
Z indeksem klastrowym:
USE tempdb;
GO
--Create a table with a primary key
CREATE TABLE dbo.TestDB
( ID int IDENTITY(1,1) PRIMARY KEY,
TestColumn1 datetime2 NOT NULL,
TestColumn2 nvarchar(100) NOT NULL,
TestColumn3 int NOT NULL
);
GO
-- Query sys.indexes to view the structure
SELECT * FROM sys.indexes WHERE OBJECT_NAME(object_id) = N'TestDB';
GO
SELECT * FROM sys.key_constraints WHERE OBJECT_NAME(parent_object_id) = N'TestDB';
GO
-- Insert some data into the table
SET NOCOUNT ON;
DECLARE @Counter int = 0;
WHILE @Counter < 10000 BEGIN
INSERT dbo.TestDB (TestColumn1, TestColumn2, TestColumn3)
VALUES(SYSDATETIME(),'999-9999',CAST(RAND() * 1000 AS int));
SET @Counter += 1;
END;
GO
-- Modify the data in the table
SET NOCOUNT ON;
DECLARE @Counter int = 0;
WHILE @Counter < 10000 BEGIN
UPDATE dbo.TestDB SET TestColumn2 = REPLICATE('9',CAST(RAND() * 100 AS int))
WHERE ID = @Counter % 10000;
IF @Counter % 100 = 0 PRINT @Counter;
SET @Counter += 1;
END;
GO
--Cleaning
DROP TABLE dbo.TestDB;
GO
Jak widać założenie klucza podstawowego (a za nim indeksu klastrowego) poprawiło kilkunastokrotnie czas wykonania zapytania.