It all depends on the definitions and the key (and non-key) columns defined in the nonclustered index. The clustered index is the actual table data. Therefore it contains all of the data in the data pages, whereas the nonclustered index is only containing columns' data as defined in the index creation DDL.
Let's set up a test scenario:
use testdb;
go
if exists (select 1 from sys.tables where name = 'TestTable')
begin
drop table TestTable;
end
create table dbo.TestTable
(
id int identity(1, 1) not null
constraint PK_TestTable_Id primary key clustered,
some_int int not null,
some_string char(128) not null,
some_bigint bigint not null
);
go
create unique index IX_TestTable_SomeInt
on dbo.TestTable(some_int);
go
declare @i int;
set @i = 0;
while @i < 1000
begin
insert into dbo.TestTable(some_int, some_string, some_bigint)
values(@i, 'hello', @i * 1000);
set @i = @i + 1;
end
So we've got a table loaded with 1000 rows, and a clustered index (PK_TestTable_Id
) and a nonclustered index (IX_TestTable_SomeInt
). As you've seen in your testing, but just for thoroughness:
set statistics io on;
set statistics time on;
select some_int
from dbo.TestTable -- with(index(PK_TestTable_Id));
set statistics io off;
set statistics time off;
-- nonclustered index scan (IX_TestTable_SomeInt)
-- logical reads: 4
Here we have a nonclustered index scan on the IX_TestTable_SomeInt
index. We have 4 logical reads for this operation. Now let's force the clustered index to be used.
set statistics io on;
set statistics time on;
select some_int
from dbo.TestTable with(index(PK_TestTable_Id));
set statistics io off;
set statistics time off;
-- clustered index scan (PK_TestTable_Id)
-- logical reads: 22
Here with the clustered index scan we have 22 logical reads. Why? Here's why. It all matters on how many pages that SQL Server has to read in order to grab the entire result set. Get the average row count per page:
select
object_name(i.object_id) as object_name,
i.name as index_name,
i.type_desc,
ips.page_count,
ips.record_count,
ips.record_count / ips.page_count as avg_rows_per_page
from sys.dm_db_index_physical_stats(db_id(), object_id('dbo.TestTable'), null, null, 'detailed') ips
inner join sys.indexes i
on ips.object_id = i.object_id
and ips.index_id = i.index_id
where ips.index_level = 0;
Take a look at my result set of the above query:
As we can see here, there are an average of 50 rows per page on the leaf pages for the clustered index, and an average of 500 rows per page on the leaf pages for the nonclustered index. Therefore, in order to satisfy the query more pages need to be read from the clustered index.
In this case, yes, they appear to be redundant. The second index will satisfy any queries that would benefit from the first one. Also, both indexes appear to contain the same columns, so rows within the index leaf pages should be about the same size in either. Your non-leaf index pages will be a bit larger with more key columns, but that probably won't amount to a significant size increase, unless the index rows are quite large and only fit a few per page.
It's important to consider the size of the index rows when deciding if an index is truly redundant. If you have an index on a couple of int columns, a decimal, and a datetime, the rows will be much smaller than if you have another index on those same columns plus a character column that averages a couple hundred bytes. Any analytical queries that would aggregate a large number of rows would benefit from the index that has rows much more densely packed on the data pages, even though either index could technically satisfy the query.
So, make sure you consider the difference in average row size, and whether or not that has any performance implications before you go dropping any redundant-looking indexes.
Best Answer
Not quite.
If the PK is A,B,C, you already have that unique index.
A,B,C,D is a covering index for queries that need exactly those four columns, but would be better as A,B,C INCLUDE D
A,D,B,C is definitely not redundant; it's the only one that can work with both A and D on queries that use those two but don't use B or C in the index. Depending on the cardinality of A and D, it might be critical, or worthless... but it's not redudant.
Remember, SQL Server starts at the left and goes to the right; when it hits a column that's not used, it stops.