SQL Server不会使用我的索引

时间:2009-07-09 19:54:36

标签: sql-server sql-server-2005 performance indexing

我有一个相当简单的查询:

SELECT
     col1,
     col2…
FROM
     dbo.My_Table
WHERE
     col1 = @col1 AND
     col2 = @col2 AND
     col3 <= @col3

它表现得非常糟糕,所以我在col1,col2,col3(int,bit和datetime)上添加了一个索引。当我检查查询计划时,它忽略了我的索引。我尝试在每个可能的配置中重新排序索引中的列,它总是忽略索引。当我运行查询时,它执行聚簇索引扫描(表大小介于700K和800K行之间)并需要10-12秒。当我强制它使用我的索引时,它会立即返回。我小心翼翼地清除测试之间的缓存和缓冲区。

我尝试过的其他事情:

UPDATE STATISTICS dbo.My_Table

CREATE STATISTICS tmp_stats ON dbo.My_Table (col1, col2, col3) WITH FULLSCAN

我在这里遗漏了什么?我讨厌在存储过程中放置​​一个索引提示,但SQL Server似乎无法从中获得这个问题的线索。任何人都知道可能阻止SQL Server识别使用索引的任何其他事情是个好主意吗?

编辑:返回的其中一列是TEXT列,因此使用覆盖索引或INCLUDE将不起作用:(

8 个答案:

答案 0 :(得分:13)

你有80万行,由col1,col2,col3索引。 Col2有点,因此其选择性为50%。 Col3是在范围内检查的(&lt; =),因此它的选择性也大致在50%左右。留下col1。查询是针对通用的参数化计划编译的,因此必须考虑一般情况。如果你有10个不同的col1值,那么你的索引将返回大约800k / 10 * 25%,大约是20k键,在聚簇索引中查找以检索'...'部分。如果你有10k个不同的col1值,那么索引将只返回20个键来查找。正如您所看到的,重要的不是您在这种情况下如何构建索引,而是实际数据。根据col1的选择性,优化器将根据聚簇索引扫描选择一个计划(优于20k密钥查找,每次查找的成本至少 3-5页读取)或一个基于非聚集索引(如果col1足够有选择性)。在现实生活中,col1的分布也起着一定的作用,但进入这一点会使解释过于复杂。

您可以获得事后的好处,并声称该计划是错误的,但该计划是基于编译时可用数据的最佳成本估算。您可以使用提示来影响它(如您所建议的那样提供索引提示,或者像Quassnoi建议的那样优化提示),但是您的查询可能对您的测试集执行得更好,而对于不同的数据集则更糟糕,例如@ col1时的情况= <the value that matches 500k records>。您还可以覆盖索引,从而消除投影列表中需要进行聚簇索引查找的“...”,在这种情况下,非聚集索引始终比聚簇扫描更好地进行成本匹配。

金伯利·特里普有一篇关于这个主题的博客文章,她称之为“index tipping point”,它解释了如何忽略一个看似完美的候选索引:一个未涵盖的非聚集索引投影列表和选择性差将被视为比集群扫描更昂贵。

答案 1 :(得分:2)

SQL Server优化器不适合优化使用变量的查询。

如果您确定您将始终从使用索引中受益,请添加提示。

如果您将文字值放入查询而不是变量,它将选择正确的统计信息并使用索引。

您也可以尝试添加更轻的提示:

OPTION (OPTIMIZE FOR (@col1 = 1, @col2 = 0, @col3 = '2009-07-09'))

,它将使用统计数据计算这些变量值的最佳执行计划,并且无论如何都不会坚持使用索引。

答案 2 :(得分:1)

索引的顺序对此查询很重要:

CREATE INDEX MyIndex ON MyTable (col3 DESC, col2 ASC, col1 ASC)

ASC / DESC并不是当sql server匹配where子句时,它可以首先匹配col3而沿着该值遍历索引

答案 3 :(得分:1)

您是否尝试过从索引中删除该位?

create index ix1 on My_Table(Col3, Col1) INCLUDE(Col2) 
-- include other columns from the select list if needed

此外,您已从选择列表中省略了其余列。如果索引中没有多少,或者为INCLUDE语句创建查询的覆盖索引,您可能需要考虑包括那些。

答案 4 :(得分:1)

尝试屏蔽参数以防止参数嗅探:

CREATE PROCEDURE MyProc AS
    @Col1 INT
    -- etc...
AS
    DECLARE @MaskedCol1 INT
    SET @MaskedCol1 = @Col1
    -- etc...

    SELECT
         col1,
         col2…
    FROM
         dbo.My_Table
    WHERE
         col1 = @MaskecCol1 AND
         -- etc...

听起来很愚蠢,但我看到SQL服务器因为参数嗅探而做了一些奇怪的事情。

答案 5 :(得分:1)

我敢打赌,SQL Server认为从聚簇索引中获取其余列(在您的示例中由...指定)的价格超过索引的好处,因此它只扫描聚簇键。如果是这样,看看你是否可以将其作为覆盖索引。

或者它是否使用另一个索引?

答案 6 :(得分:0)

列是否可以为空?有时Sql Server认为它必须扫描表以查找NULL值。

尝试在查询中添加“和col1不为空”,它可以使sqlserver使用索引并提示。

另外,检查统计数据是否真的是最新的:

SELECT 
    object_name = Object_Name(ind.object_id),
    IndexName = ind.name,
    StatisticsDate = STATS_DATE(ind.object_id, ind.index_id)
FROM SYS.INDEXES ind
order by STATS_DATE(ind.object_id, ind.index_id) desc

答案 7 :(得分:0)

如果SELECT返回的索引不在索引SQL中,我发现扫描聚簇索引更有效,而不必进行密钥查找以查找您请求的其他值。

如果您有TEXT列,请尝试将数据类型切换为VARCHAR(MAX),然后将值包含在非聚集索引中。