Blog Post

Unique and non-unique non-clustered indexes on a non-unique clustered index

,

In the last weblog post I have talked about the differences in unique and non-unique

non-clustered indexes on a unique clustered index. In this weblog post I want to talk

about the differences of non-clustered indexes defined on a non-unique clustered index.

As you already know from this posting,

SQL Server handles non-unique clustered indexes internally different as unique clustered

indexes. If you define a non-unique clustered index, SQL Server adds the so called uniquifier to

your index records, which leads to a 4 byte overhead per each index row in the navigation

structure of your clustered index.

The following listing creates again our customers table, defines this time a non-unique

clustered index on it, and finally two non-clustered indexes, where one of them is

unique and the other is non-unique.

-- Create a table

with 393 length + 7 bytes overhead = 400 bytes

-- Therefore

20 records can be stored on one page (8.096 / 400) = 20,24

CREATE TABLE Customers

(

CustomerID INT NOT NULL,

CustomerName CHAR(100) NOT NULL,

CustomerAddress CHAR(100) NOT NULL,

Comments CHAR(189) NOT NULL

)

GO

-- Create a non

unique clustered index on the previous created table

CREATE CLUSTERED INDEX idx_Customers ON Customers(CustomerID)

GO

-- Insert

80.000 records

DECLARE @i INT = 1

WHILE (@i <= 20000)

BEGIN

DECLARE @j INT = 1

INSERT INTO Customers VALUES

(

@i,

'CustomerName' + CAST(@i AS CHAR) + CAST(@j AS CHAR),

'CustomerAddress' + CAST(@i AS CHAR),

'Comments' + CAST(@i AS CHAR)

)

SET @j += 1;

INSERT INTO Customers VALUES

(

@i,

'CustomerName' + CAST(@i AS CHAR) + CAST(@j AS CHAR),

'CustomerAddress' + CAST(@i AS CHAR),

'Comments' + CAST(@i AS CHAR)

)

SET @j += 1;

INSERT INTO Customers VALUES

(

@i,

'CustomerName' + CAST(@i AS CHAR) + CAST(@j AS CHAR),

'CustomerAddress' + CAST(@i AS CHAR),

'Comments' + CAST(@i AS CHAR)

)

SET @j += 1;

INSERT INTO Customers VALUES

(

@i,

'CustomerName' + CAST(@i AS CHAR) + CAST(@j AS CHAR),

'CustomerAddress' + CAST(@i AS CHAR),

'Comments' + CAST(@i AS CHAR)

)

SET @i += 1

END

GO

-- Create a unique

non clustered index on the clustered table

CREATE UNIQUE NONCLUSTERED INDEX idx_UniqueNCI_CustomerID

ON Customers(CustomerName)

GO

-- Create a non-unique

non clustered index on the clustered table

CREATE NONCLUSTERED INDEX idx_NonUniqueNCI_CustomerID

ON Customers(CustomerName)

GO

When you now do a short look into the sys.dm_db_index_physical_stats DMV,

you can see that the unique non-clustered index takes 107 bytes per index row in the

navigation levels, where the non unique non-clustered index takes 117 bytes on the

average (minimum 111 bytes, maximum 117 bytes). Let's analyze the differences and

dump out the index root page of the unique non-clustered index through the DBCC

PAGE command:

DBCC PAGE(NonUniqueClusteredIndexStructure_NonClusteredIndex, 1, 4529, 3)

GO

As you can see from the following picture SQL Server only stores the unique non-clustered

key in the index root level (and also in the intermediate levels), because the non-clustered

key is already unique by itself:

image

When you examine the byte by byte representation of the unique non-clustered index

record, you can see that SQL Server uses here the following bytes:

  • 1 Byte: Status Bits

  • n Bytes: Unique Non-Clustered Index Key – in this case 100 bytes

  • 4 Bytes: PageID

  • 2 Bytes: FileID

When you dump out the leaf-level of the unique non-clustered index, you can see that

SQL Server points through the non-unique clustered key and the uniquifier to the correct

record in the clustered table:

image

The conclusion here is that the unique non-clustered index on a non-unique clustered

index makes only the 4 byte overhead in the leaf level of the clustered index, because

here SQL Server directly points to the correct record. There is no additional overhead

involved in the non-leaf levels of the unique non-clustered index.

Let's now dump out the index root page of the non-unique non-clustered index defined

on our non-unique clustered index:

image

This is now a really interesting output! The key of the index record must be by design

unique. How can SQL Server make a non-unique non-clustered index key unique? Easy

- SQL Server just adds the clustered index key (4 bytes). But the clustered index

key is also not unique by default, therefore SQL Server also adds the uniquifier (4

bytes), so you have an resulting overhead of 8 bytes per index row, when the uniquifier

is not equal to 0. When the uniquifier is equal to 0, you get an overhead of 4 bytes,

because in this case the uniquifier is not stored physically in the index record,

and a 0 is assumed by SQL Server automatically. When you again analyze the byte by

byte representation you can see the following bytes:

  • 1 Byte: Status Bits

  • n Bytes: Unique Non-Clustered Index Key – in this case 100 bytes

  • n Bytes: Non-Unique Clustered Index Key – in this case 4 bytes

  • 4 Bytes: PageID

  • 2 Bytes: FileID

  • 4 Bytes: Some bytes used by the uniquifier

  • 4 Bytes: The uniquifier value itself, when it is not equal to 0

The minimum length of the index record is therefore 111 bytes and the maximum length

is 117 bytes already found out earlier through the sys.dm_db_index_physical_stats DMV.

When you finally dump out the leaf-level of the non-unique non-clustered index, you

get the following result:

image

As you can see the leaf-level is the same as the leaf-level in a unique non-clustered

index defined on a non unique clustered index. The leaf level just points through

the clustered key (CustomerID) and the uniquifier to the correct

record in the clustered table. This example has shown you that there is huge overhead

(8 bytes per index row) when you define a non-unique non-clustered index on a non-unique

clustered index, because SQL Server must make the non-clustered index records internally

unique and needs therefore several bytes of storage overhead. You can download the

T-SQL script for this posting here.

-Klaus

Rate

You rated this post out of 5. Change rating

Share

Share

Rate

You rated this post out of 5. Change rating