The last UNION in SQL ignores existing INDEX

旧城冷巷雨未停 提交于 2019-12-11 11:24:15

问题


This is a refined problem I have been struggling with for a couple of days now.

I have a static table that I create and build indexes on which I then create a stored procedure to run against. My issue is bizarre and I will do my best to explain it.

I run the same scripts to create and execute across 194 databases...the vast majority of which run very quickly...however on a handful of databases they run exceptionally slow.

Just so we are clear here are the INDEX:

CREATE UNIQUE CLUSTERED INDEX IX_ID
ON DC_DuplicateMatch(ID)
go
CREATE INDEX IX_LastName_FirstName
ON DC_DuplicateMatch(ID, LastName, FirstName)
GO
CREATE INDEX IX_LastName_PostalCode
ON DC_DuplicateMatch(ID, LastName, PostalCode)
GO
CREATE INDEX IX_LastName_YearBorn 
ON DC_DuplicateMatch(ID, LastName, YearBorn)
GO
CREATE INDEX IX_FirstName_PostalCode 
ON DC_DuplicateMatch(ID, FirstName, PostalCode)
GO
CREATE INDEX IX_FirstName_YearBorn 
ON DC_DuplicateMatch(ID, FirstName, YearBorn)
GO
CREATE INDEX IX_PostalCode_YearBorn 
ON DC_DuplicateMatch(ID, PostalCode, YearBorn)
GO

And here is the the Stored Procedure:

CREATE PROC dbo.DC_GetPotentialDuplicates
    @ID    int,   
    @FirstName  varchar(30),   
    @LastName   varchar(30),   
    @PostalCode varchar(10),
    @YearBorn   varchar(4)    
AS
SELECT *  
FROM    DC_DuplicateMatch WITH(INDEX(IX_LastName_FirstName))
WHERE   ID > @ID AND 
        (LastName   = @LastName   AND 
        FirstName  = @FirstName)
UNION            
SELECT  *
FROM    DC_DuplicateMatch WITH(INDEX(IX_LastName_PostalCode))
WHERE   ID > @ID AND 
        (LastName   = @LastName   AND 
        PostalCode = @PostalCode)
UNION            
SELECT  *
FROM    DC_DuplicateMatch WITH(INDEX(IX_LastName_YearBorn))
WHERE   ID > @ID AND 
        (LastName   = @LastName   AND 
        YearBorn   = @YearBorn)
UNION
SELECT  *
FROM    DC_DuplicateMatch WITH(INDEX(IX_FirstName_PostalCode))
WHERE   ID > @ID AND 
        (FirstName  = @FirstName  AND 
        PostalCode = @PostalCode)
UNION            
SELECT  *
FROM    DC_DuplicateMatch WITH(INDEX(IX_FirstName_YearBorn))
WHERE   ID > @ID AND 
        (FirstName  = @FirstName  AND 
        YearBorn   = @YearBorn)
UNION
SELECT  *
FROM    DC_DuplicateMatch WITH(INDEX(IX_PostalCode_YearBorn))
WHERE   ID > @ID AND 
        (PostalCode = @PostalCode AND 
        YearBorn   = @YearBorn)
GO

Table Definition

ID          int no  4  10   0   
FirstName   varchar no  30              
LastName    varchar no  30          
PostalCode  char    no  10          
YearBorn    varchar no  4           

This proc consistently runs faster on larger tables...smaller tables "occasionally" run slower. Speeds range from 4,000-records/second being "fast" down to 70-records/second being "slow".

The thing is if I add in blank filler records to the target table at some point, without any other changes, the speed increases from 70 upwards closer to the 4,000 mark. It's as if the query plan is not not being built properly based on the number of records in the table.

After running both the Database Engine Tuner and Performance Monitor I have discovered the problem is SQL ignores the INDEX on the last UNION and does a table scan on some queries. And the Execution Plan specifically says I need to create the exact index I already have on the table (hence the INDEX Hints).

So I removed the 6th select UNION

The issue remained; however this time it complained that the missing index was again on the last table of the unions (which is the 5th listed above and worked without issue when there where 6-select UNIONS).

Any thoughts on why this is happening or what I can do to avoid it? (aside from adding in dummy blank records to increase table size or creating a fake 7th final union...both of which increase performance).


回答1:


The existing indexes aren't that good because ID is the leading column. That makes them unseekable in this case. Here's a better set:

CREATE INDEX IX_LastName_FirstName
ON DC_DuplicateMatch(LastName, FirstName, ID) INCLUDE (PostalCode, YearBorn)
GO
CREATE INDEX IX_LastName_PostalCode
ON DC_DuplicateMatch(LastName, PostalCode, ID) INCLUDE (FirstName, YearBorn)
GO
CREATE INDEX IX_LastName_YearBorn 
ON DC_DuplicateMatch(LastName, YearBorn, ID) INCLUDE (FirstName, PostalCode)
GO
CREATE INDEX IX_FirstName_PostalCode 
ON DC_DuplicateMatch(FirstName, PostalCode, ID) INCLUDE (LastName, YearBorn)
GO
CREATE INDEX IX_FirstName_YearBorn 
ON DC_DuplicateMatch(FirstName, YearBorn, ID) INCLUDE (LastName, PostalCode)
GO
CREATE INDEX IX_PostalCode_YearBorn 
ON DC_DuplicateMatch(PostalCode, YearBorn, ID) INCLUDE (LastName, FirstName)
GO

These are perfect for this query.

That's 6 index seeks per query. We'd need to pull some serious tricks to beat this.




回答2:


In my experience, most of the time when the query planner doesn't use an appropriate index its because update statistics needs to be rerun to have the optimizer work correctly. This seems symptomatic of the fact that your query plan is bad for just some values.

Since you're providing an index hint though, this seems unlikely- normally a hint would force the planner to use that index, but maybe that depends on your vendor.



来源:https://stackoverflow.com/questions/20408789/the-last-union-in-sql-ignores-existing-index

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!