• subscribe

Why SQL Server Lets You Create Redundant Indexes


Posted @ 10/18/2010 9:52 AM By Kimberly L. Tripp

 

Q: Why does SQL Server let me create the same (i.e., completely identical) index more than once?

A: I agree, this behavior is annoying, and if it were completely up to me, I wouldn’t allow it. At a minimum, I wish there were a configuration option that you could turn on that would prevent this behavior from happening. However, I do understand the backward compatibility reason for this behavior.

If an index is explicitly named in a hint like

   SELECT … 
   FROM table WITH (INDEX (indexname)) 
   WHERE …

the query would fail if the index were dropped, which could break existing applications. I know that index hints aren’t generally recommended, but they still must be supported and work. Therefore, SQL Server can’t drop an existing (and duplicate) index. But, why does SQL Server allow new (and duplicate) indexes to be created? Again, you could make the argument that scripts could have these problems in them already and code could be written to use these index “hints.” So, ultimately, it’s been a bad practice in the past and to make sure that no applications are broken, the bad practice needs to be supported now. Don’t get me wrong, I don’t think the practice should be allowed, but that’s the reason it is.

Related Content:

Comments

Add A Comment
  • Posted @ December 30, 2010 11:19 AM by sleakbug

    Is there any other mechanism that can be exploited in SQL 2008 (not R2 specifically) that could be used to prevent dupes? I regularly use a script to add missing indexes and would like to "lock down" the index creation to eliminate unintentional index and statistic dupes from tuning and manual operations by the DBA and developer teams?
    Thanks! You folks are my SQL heroes!

  • Posted @ November 06, 2010 11:22 AM by russellb

    Right. Thanks Paul. That's something I forgot about until I realized that my rowcounts for sys.index_columns was twice what I'd expected.

    Naturally I tested on an empty table. The overhead of creating and rolling back the index might not be acceptable on large tables.

  • Posted @ November 06, 2010 10:52 AM by Paul S. Randal

    The problem with using a DDL trigger is that it is an AFTER trigger, not an INSTEAD OF trigger. So although a trigger would 'prevent' this, the index would still be created, and then it would be rolled-back.

  • Posted @ November 06, 2010 08:54 AM by russellb

    If one wishes to disallow this, a database trigger would do the trick:
    [code]
    CREATE TRIGGER test ON DATABASE
    FOR CREATE_INDEX
    AS

    SET NOCOUNT ON;

    DECLARE @xmlEventData XML
    SET @xmlEventData = eventdata()

    Declare @table sysname
    Declare @indexColumns table (col sysname)
    Declare @cmdText varchar(512)
    Declare @tempStr varchar(512)
    Declare @newIxColCount int
    Declare @oldIxColCount int

    select @table = Convert(sysname, @xmlEventData.query('data(/EVENT_INSTANCE/TargetObjectName)'))
    select @cmdText = Convert(varchar(512), @xmlEventData.query('data(/EVENT_INSTANCE/TSQLCommand)'))

    --print @cmdText

    SELECT @tempStr =
    LTRIM(RTRIM(REPLACE(REPLACE(
    SUBSTRING(@cmdText,
    charindex(@table, @cmdText) + len(@table),
    charindex(')', @cmdText, charindex(@table, @cmdText) + len(@table))
    ),
    '(', ''),
    ')', '')
    ));

    WITH indexColumns(n, start, stop) AS (
    SELECT 1, 1, CHARINDEX(',', @tempStr)
    UNION ALL
    SELECT n + 1, stop + 1, CHARINDEX(',', @tempStr, stop + 1)
    FROM indexColumns
    WHERE stop > 0
    )
    INSERT @indexColumns
    SELECT LTRIM(RTRIM(SUBSTRING(@tempStr, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END))) FROM indexColumns

    SELECT @newIxColCount = @@ROWCOUNT

    --select * from sys.columns WHERE object_id = object_id(@table)
    --SELECT * FROM sys.index_columns WHERE object_id = object_id(@table)
    --select * from @indexColumns

    SELECT @oldIxColCount = count(*)
    FROM sys.columns c
    JOIN sys.index_columns ic
    On c.column_id = ic.column_id
    And c.object_id = ic.object_id
    FULL OUTER JOIN
    @indexColumns new
    On new.col = c.name
    WHERE c.object_id = object_id(@table);


    IF @newIxColCount * 2 = @oldIxColCount
    BEGIN
    Declare @err varchar(312)
    SET @err = 'Index covering exact columns on [' + @table + '] already exists. Index creation failed'
    RaisError (@err, 16, 1)
    Rollback
    END

    --print @oldIxColCount
    --print @newIxColCount
    GO
    [/code]

  • Posted @ October 21, 2010 04:42 PM by Robert L Davis

    Perhaps adding a warning would be an improvement.

You must log on before posting a comment.

Are you a new visitor? Register Here


More about Kimberly and Paul . . .

Paul worked on the SQL Server team for nine years in development and management roles, writing many of the DBCC commands, and he was ultimately responsible for SQL Server 2008’s core storage engine. Paul blogs at SQLskills.com/blogs/Paul.

Kimberly worked on the SQL Server team in the early 1990s as a tester and writer before leaving to found SQLskills and embrace her passion for teaching and consulting. Kimberly blogs at SQLskills.com/blogs/Kimberly.

They've written Microsoft white papers and books for SQL Server 2008, 2005 and 2000, and they're regular, top-rated presenters worldwide on database maintenance, high availability, disaster recovery, design, performance tuning, and SQL Server internals. Together they teach SQLskills Immersion Events, which are wildly popular classes that teach the essential skills required to be an effective DBA; these classes also cover the required topics of the Microsoft Certified Master (MCM) certification.