Difference in performance between clustered and non-clustered indexes - sql-server

I found a table in DB with two separate indexes over the same column. The column type is int and there is a clustered primary key on this column. In addition to this, there is a unique non-clustered index on the same column. Indexes have the same options (sort direction and others) and don't contain any included columns.
This index is used by foreign key constraints in some other tables, thus I can't drop it without recreating foreign key constraints.
Could be any sane reason for this?

Maybe it is for efficiency. The nonclustered index is normally smaller than the clustered index, because the clustered index at the leaf level contains all the (non-LOB) fields. So maybe it prefers to use the nonclustered index to enforce the foreign key constraints.
Update: I have done some further tests using AdventureWorks database, which bear out this theory. See below.
I can reproduce the problem using two tables T1 and T2. T1 is the parent and there is a foreign key relationship from T2 to T1.
When T1 has a clustered primary key constraint and a nonclustered unique index Ix-T1, I can alter the table and drop the clustered primary key constraint, but I can't drop Ix-T1 as you found.
If I make T1 with a nonclustered primary key constraint and a clustered unique index Ix_T1, then the situation is reversed: I can drop Ix-T1, but I can't remove the primary key constraint.
CREATE TABLE T1
(
id int NOT NULL CONSTRAINT PK_T1 PRIMARY KEY CLUSTERED
);
CREATE UNIQUE NONCLUSTERED INDEX Ix_T1
ON T1(id);
CREATE TABLE T2
(
id2 int NOT NULL PRIMARY KEY CLUSTERED,
id1 int NOT NULL FOREIGN KEY REFERENCES dbo.T1(id)
);
INSERT INTO T1 (id)
VALUES (1), (2), (3), (4);
INSERT INTO T2 (id2, id1)
VALUES (11, 1), (12, 2), (13, 3);
Try to drop the nonclustered index. This fails.
DROP INDEX Ix_T1
ON dbo.T1;
However I can drop the clustered primary key constraint.
ALTER TABLE dbo.T1
DROP CONSTRAINT PK_T1;
Repeat the test with T1 having a nonclustered primary key and a clustered unique index.
CREATE TABLE T1
(
id int NOT NULL CONSTRAINT PK_T1 PRIMARY KEY NONCLUSTERED
);
CREATE UNIQUE CLUSTERED INDEX Ix_T1
ON T1(id);
This time, I cannot drop the primary key constraint.
ALTER TABLE dbo.T1
DROP CONSTRAINT PK_T1;
However I can drop the clustered index.
DROP INDEX Ix_T1
ON dbo.T1;
So, if my theory is correct, the performance could suffer if you remove the nonclustered index. You might want to do some investigation and tests.
Is there any documentation for the database schema explaining why the index exists? Or can you ask the person who designed the database?
I've done some further tests using AdventureWorks2014, which bear out my theory.
USE AdventureWorks2014;
GO
CREATE SCHEMA test;
GO
-- Create two test tables
SELECT *
INTO test.SalesOrderHeader
FROM Sales.SalesOrderHeader;
SELECT *
INTO test.SalesOrderDetail
FROM Sales.SalesOrderDetail;
-- Test 1 - Clustered primary key and nonclustered index
ALTER TABLE test.SalesOrderHeader
ADD CONSTRAINT PK_Test_SalesOrderHeader PRIMARY KEY CLUSTERED (SalesOrderID);
CREATE UNIQUE NONCLUSTERED INDEX Ix_Test_SalesOrderHeader
ON test.SalesOrderHeader(SalesOrderID);
-- Test 2 - Nonclustered primary key and clustered index
CREATE UNIQUE CLUSTERED INDEX Ix_Test_SalesOrderHeader
ON test.SalesOrderHeader(SalesOrderID);
ALTER TABLE test.SalesOrderHeader
ADD CONSTRAINT PK_Test_SalesOrderHeader PRIMARY KEY NONCLUSTERED (SalesOrderID);
-- Test 3 - Clustered primary key only
ALTER TABLE test.SalesOrderHeader
ADD CONSTRAINT PK_Test_SalesOrderHeader PRIMARY KEY CLUSTERED (SalesOrderID);
-- Same for all tests
ALTER TABLE test.SalesOrderDetail
ADD CONSTRAINT PK_Test_SalesOrderDetail PRIMARY KEY CLUSTERED (SalesOrderDetailID);
ALTER TABLE test.SalesOrderDetail
ADD CONSTRAINT FK_Test_SalesOrderDetail_SalesOrderHeader FOREIGN KEY (SalesOrderID) REFERENCES test.SalesOrderHeader(SalesOrderID);
-- Update 100 records in SalesOrderDetail
UPDATE test.SalesOrderDetail
SET SalesOrderID = SalesOrderID + 1
WHERE SalesOrderDetailID BETWEEN 57800 AND 57899;
Actual execution plan for test 1.
Actual execution plan for test 2. The estimated subtree cost for the Index Seek operator is almost identical to test 1.
Actual execution plan for test 3. The estimated subtree cost for the Index Seek is more than double test 1 or test 2.
And here is a query that measures the sizes of the indexes. (Test 1 configuration.) You can clearly see that the clustered index is much bigger.
-- Measure sizes of indexes
SELECT I.object_id, I.name, I.index_id, I.[type], I.[type_desc], SUM(s.used_page_count) * 8 AS 'IndexSizeKB'
FROM sys.indexes AS I
INNER JOIN sys.dm_db_partition_stats AS S
ON S.[object_id] = I.[object_id] AND S.index_id = I.index_id
WHERE I.[object_id] = OBJECT_ID('test.SalesOrderHeader')
GROUP BY I.object_id, I.name, I.index_id, I.[type], I.[type_desc];
Here are some references that explain clustered indexes and nonclustered indexes.
TechNet > Tables and Index Data Structures Architecture: https://technet.microsoft.com/en-us/library/ms180978(v=sql.105).aspx
Training kit 70-462 Administering Microsoft SQL Server 2012 Databases > Chapter 10: Indexes and Concurrency > Lesson 1: Implementing and Maintaining Indexes
Microsoft SQL Server 2012 Internals by Kalen Delaney > Chapter 7: Indexes: internals and management

Related

Is there any way to mark existing clustered index as primary key in SQL Server?

I have table with about 60 000 000 rows in it and having size near 60 GB.
It has 2 indexes: clustered index and primary key on same id identity column.
Primary key index has size near 1GB. It looks excessive. I have several such tables.
Question is, is there any way to effectively mark existing clustered index as primary key also without dropping both indexes and creating one single new index?
Sub question is does it worth it to do such operations, maybe only drop primary keys? What's the real practical advantage of having primary key except descriptive usage for 3rd party tools? maybe sql server optimizer uses this metadata to optimize queries or any other advantages that I am missing?
Small sample of what I want to achieve, but other way (if it exists), without dropping and creating indexes.
I assume that it looks like there's no other way really, but who knows maybe there's some trick.
create table a
(
id int identity,
col1 varchar(50)
)
create unique clustered index cix_id on a(id)
alter table a add constraint pk_a primary key nonclustered(id)
select t.name,i.name,i.is_primary_key,i.type_desc from
sys.tables t
inner join sys.indexes i on i.object_id=t.object_id
where t.name='a'
drop index cix_id on a
alter table a drop constraint pk_a
alter table a add constraint pk_a primary key clustered(id)
select t.name,i.name,i.is_primary_key,i.type_desc from
sys.tables t
inner join sys.indexes i on i.object_id=t.object_id
where t.name='a'

SQL Server - Change clustered index without dropping primary key?

I want to change the clustered index on a table to a column combo other than the primary key.
How can I drop the clustered index, keep the same primary key I have, and add a new clustered index on a new set of columns.
Final result - Non-clustered indexed PK, clustered index on new set of columns.
The only way you can do this is:
1) First Drop the constraints with the below code:
ALTER TABLE TABLENAME DROP CONSTRAINT ConstrainName
2) Then Create the Clustered Index on the Column you want with the below code:
Create clustered index Index_Name on TableName (column1,column2)
3) Create a primary key on a column :
ALTER TABLE TableName
ADD CONSTRAINT constraint_name PRIMARY KEY (ColumnName);

Can I make Primary Key non clustered while another index is clustered?

In my table I have ID as primary key, it is just a meaningless unique code
as it is a primary key SQL Server 2017 made it clustered.
I have another column in my table called myTime this is a timestamp with non uinique non clustered index
Can I make the PK a non clustered and the index is clustered and how?
Yes you can. If you already have an existing table then you need to:
Drop the current clustered PRIMARY KEY
Create your CLUSTERED INDEX
Create a PRIMARY KEY NONCLUSTERED
For example:
IF OBJECT_ID('tempdb..#Test') IS NOT NULL
DROP TABLE #Test
CREATE TABLE #Test (
ID INT,
TimeStamp DATETIME,
CONSTRAINT PK_Test PRIMARY KEY (ID)) -- Clustered by default
ALTER TABLE #Test DROP PK_Test
CREATE CLUSTERED INDEX CI_Test_TimeStamp ON #Test (TimeStamp)
ALTER TABLE #Test ADD CONSTRAINT PK_Test PRIMARY KEY NONCLUSTERED (ID)
The only thing that will enforce uniqueness is the PRIMARY KEY constraint, you can still have a clustered index on repeated values, although it might raise an eyebrow for performance. See Eric's link for details.
Yes you can, by specifying the primary key be nonclustered.
ALTER TABLE TableName
ADD CONSTRAINT PK_name PRIMARY KEY NONCLUSTERED (ID);
You make another index clustered by specifying a clustered index.
CREATE CLUSTERED INDEX IX_Name
ON dbo.TableName (ColumnName);

Is Unique key Clustered or Non-Clustered Index in SQL Server?

I am new to SQL Server and while learning about clustered index, I got confused!
Is unique key clustered or a non-clustered index? Unique key holds only unique values in the column including null, so according to this concept, unique key should be a clustered index, right? But when I went through this article I got confused MSDN
When you create a UNIQUE constraint, a unique nonclustered index is
created to enforce a UNIQUE constraint by default. You can specify a
unique clustered index if a clustered index on the table does not
already exist.
Please help me to understand the concept in a better manner, Thank you.
There are three ways of enforcing uniqueness in SQL Server indexes.
Primary Key constraint
Unique constraint
Unique index (not constraint based)
Whether they are clustered or non clustered is orthogonal to whether or not the indexes are declared as unique using any of these methods.
All three methods can create a clustered or non clustered index.
By default the unique constraint and Unique index will create a non clustered index if you don't specify any different (and the PK will by default be created as CLUSTERED if no conflicting clustered index exists) but you can explicitly specify CLUSTERED/NONCLUSTERED for any of them.
Example syntax is
CREATE TABLE T
(
X INT NOT NULL,
Y INT NOT NULL,
Z INT NOT NULL
);
ALTER TABLE T ADD PRIMARY KEY NONCLUSTERED(X);
--Unique constraint NONCLUSTERED would be the default anyway
ALTER TABLE T ADD UNIQUE NONCLUSTERED(Y);
CREATE UNIQUE CLUSTERED INDEX ix ON T(Z);
DROP TABLE T;
For indexes that are not specified as unique SQL Server will silently make them unique any way. For clustered indexes this is done by appending a uniquefier to duplicate keys. For non clustered indexes the row identifier (logical or physical) is added to the key to guarantee uniqueness.
Unique index can be both clustered or non-clustered.
But if you have nullable column the NULL value should be unique (only 1 row where column is null).
If you want to store more then 1 NULLs you can create the index with filter "where columnName is not null".
well all the answers provided was very helpful, but still i would like to add some detailed answer so that i would be helpful for some others as well
A table can contain only one clustered index and a primary key can
be a clustered / non-clustered index.
Unique Key can be a clustered/non-clustered index as well,
below are some of the examples
Scenario 1 : Primary Key will default to Clustered Index
In this case we will create only Primary Key and when we check the kind of index created on the table we will notice that it has created clustered index automatically over it.
USE TempDB
GO
-- Create table
CREATE TABLE TestTable
(ID INT NOT NULL PRIMARY KEY,
Col1 INT NOT NULL)
GO
-- Check Indexes
SELECT OBJECT_NAME(OBJECT_ID) TableObject,
[name] IndexName,
[Type_Desc] FROM sys.indexes
WHERE OBJECT_NAME(OBJECT_ID) = 'TestTable'
GO
-- Clean up
DROP TABLE TestTable
GO
Scenario 2: Primary Key is defined as a Non-clustered Index
In this case we will explicitly defined Primary Key as a non-clustered index and it will create it as a non-clustered index. It proves that Primary Key can be non-clustered index.
USE TempDB
GO
-- Create table
CREATE TABLE TestTable
(ID INT NOT NULL PRIMARY KEY NONCLUSTERED,
Col1 INT NOT NULL)
GO
-- Check Indexes
SELECT OBJECT_NAME(OBJECT_ID) TableObject,
[name] IndexName,
[Type_Desc] FROM sys.indexes
WHERE OBJECT_NAME(OBJECT_ID) = 'TestTable'
GO
-- Clean up
DROP TABLE TestTable
GO
Scenario 3: Primary Key defaults to Non-Clustered Index with another column defined as a Clustered Index
In this case we will create clustered index on another column, SQL Server will automatically create a Primary Key as a non-clustered index as clustered index is specified on another column.
-- Case 3 Primary Key Defaults to Non-clustered Index
USE TempDB
GO
-- Create table
CREATE TABLE TestTable
(ID INT NOT NULL PRIMARY KEY,
Col1 INT NOT NULL UNIQUE CLUSTERED)
GO
-- Check Indexes
SELECT OBJECT_NAME(OBJECT_ID) TableObject,
[name] IndexName,
[Type_Desc] FROM sys.indexes
WHERE OBJECT_NAME(OBJECT_ID) = 'TestTable'
GO
-- Clean up
DROP TABLE TestTable
GO
Scenario 4: Primary Key defaults to Clustered Index with other index defaults to Non-clustered index
In this case we will create two indexes on the both the tables but we will not specify the type of the index on the columns. When we check the results we will notice that Primary Key is automatically defaulted to Clustered Index and another column as a Non-clustered index.
-- Case 4 Primary Key and Defaults
USE TempDB
GO
-- Create table
CREATE TABLE TestTable
(ID INT NOT NULL PRIMARY KEY,
Col1 INT NOT NULL UNIQUE)
GO
-- Check Indexes
SELECT OBJECT_NAME(OBJECT_ID) TableObject,
[name] IndexName,
[Type_Desc] FROM sys.indexes
WHERE OBJECT_NAME(OBJECT_ID) = 'TestTable'
GO
-- Clean up
DROP TABLE TestTable
GO
reference:the above details is been refrenced from this article

SQL Server creating table with clustered index without a primary key

Is it possible to create a clustered index from a create table statement in SQL Server 2008 that is not a primary key?
The purpose of this is for a table in SQL Azure, so it is not an option for me to first create the table, and then create the clustered index on the table.
Edit: Apparently it was FluentMigrator that was causing my problems, it's version table does not have a clustered index so it was erroring trying to create the versioning table not my table.
Yes, it is possible to create a clustered index that is not the primary key. Just use a CREATE CLUSTERED INDEX statement.
CREATE TABLE dbo.myTable (
myTableId int PRIMARY KEY NONCLUSTERED
myColumn int NOT NULL
)
CREATE CLUSTERED INDEX myIndex ON dbo.myTable(myColumn)
Prior to version Azure SQL Database v12, you had to have a clustered index before you could insert any data to a table. As of Azure SQL Database v12, heaps (tables without a clustered index) are now supported.
If your database was created prior to June 2016, here are the instructions for upgrading to version 12.
CREATE TABLE dbo.Table_1
(
Id int NOT NULL IDENTITY (1, 1) PRIMARY KEY NONCLUSTERED,
SomeOtherUniqueColumn int NOT NULL CONSTRAINT Item4 UNIQUE CLUSTERED
) ON [PRIMARY]
note the specification of nonclustered on the primary key
This will still work.
CREATE TABLE dbo.Table_1
(
SomeOtherUniqueColumn int NOT NULL CONSTRAINT Item4 UNIQUE CLUSTERED
) ON [PRIMARY]
The code below is compatible with Azure. It creates a primary key non-clustered and a clustered index in a single create table statement. This syntax also allows for specifying more than one column in your key.
CREATE TABLE MyTable (
ID uniqueidentifier NOT NULL,
UserID uniqueidentifier NOT NULL,
EntryDate DATETIME NOT NULL,
CONSTRAINT PK_MyPrimaryKey_Name PRIMARY KEY NONCLUSTERED (ID),
CONSTRAINT UCI_MyClusteredIndexName UNIQUE CLUSTERED (UserID ASC,EntryDate ASC,ID ASC)
);
In order to change a tables clustered index, the clusteredd index must be dropped, which converts the table into a heap and then the new clustered index is applied. Because Azure does not support heaps (tables without clustered indexes) it is not possible to change the clustered index without dropping the table and recreating it. In Azure you can not specify a clustered index in any other place other than the table create statement.

Resources