SQL Server 不会使用我的索引
Posted
技术标签:
【中文标题】SQL Server 不会使用我的索引【英文标题】:SQL Server won't use my index 【发布时间】:2010-11-09 12:26:34 【问题描述】:我有一个相当简单的查询:
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 将不起作用:(
【问题讨论】:
您在列列表中添加了一个省略号,您还选择了哪些其他列?如果只有 col1、col2 和 col3 怎么办? 克里斯,好点,但是“当我强制它使用我的索引时,它会立即返回”涵盖了这一点。 省略号是因为查询返回表中的所有列。我想我可以把 SELECT * 【参考方案1】:索引的顺序对于这个查询很重要:
CREATE INDEX MyIndex ON MyTable (col3 DESC, col2 ASC, col1 ASC)
与其说是 ASC/DESC,不如说是当 sql server 去匹配 where 子句时,它可以先在 col3 上匹配,然后沿着那个值遍历索引。
【讨论】:
对于这个查询,索引的顺序应该和@Tom H. 创建的一样。 我确实尝试了几种可能的列顺序。都给出了相同的结果。【参考方案2】:SQL Server
优化器不擅长优化使用变量的查询。
如果您确定您将始终从使用索引中受益,请提供提示。
如果您将文字值而不是变量放入查询中,它将选择正确的统计信息并使用索引。
你也可以尝试放一个更轻松的提示:
OPTION (OPTIMIZE FOR (@col1 = 1, @col2 = 0, @col3 = '2009-07-09'))
,它会为这些变量的值计算出最佳的执行计划,使用统计,并且无论如何都不会坚持使用索引。
【讨论】:
如果我在 SP 之外运行查询,并且列值是硬编码的,它仍然使用聚集索引扫描 :( @Tom:能否请您发布您的确切表格定义?【参考方案3】:您是否尝试过从索引中剔除该位?
create index ix1 on My_Table(Col3, Col1) INCLUDE(Col2)
-- include other columns from the select list if needed
此外,您已从选择列表中省略了其余列。如果索引中没有很多内容,您可能需要考虑包含这些内容,或者作为 INCLUDE 语句为查询创建覆盖索引。
【讨论】:
【参考方案4】:尝试屏蔽您的参数以防止参数嗅探:
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 服务器因为参数嗅探而做了一些奇怪的事情。
【讨论】:
感谢您的建议。虽然我可以在 SP 之外运行 SELECT,但我仍然看到同样的情况。【参考方案5】:我敢打赌,SQL Server 认为从聚集索引中获取其余列(在您的示例中由 ... 指定)的代价超过了索引的好处,因此它只扫描聚集键。如果是这样,看看你是否可以把它作为一个覆盖索引。
或者它是否使用另一个索引?
【讨论】:
如果我不强制使用索引,它将使用聚集键。列列表包括表中的所有列。虽然我可以在上面放一个大的覆盖索引,但我会有效地复制表格。我需要查看 INSERT/UPDATE/DELETE 频率,看看成本是否合理。【参考方案6】:列是否可以为空?有时 Sql Server 认为它必须扫描表才能找到 NULL 值。
尝试在查询中添加“and col1 is not null”,它可以让 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
【讨论】:
与 Oracle 不同,SQL Server 也为 NULL 建立索引,并且索引始终覆盖所有行。 我的理论是,有时统计数据显示有很多行带有 NULL。然后 Sql Server 恢复到表扫描以覆盖“c1 为空”的情况。 @Andomar:此查询永远无法覆盖COL1
为NULL
的行【参考方案7】:
您有 800k 行由 col1、col2、col3 索引。 Col2 有点,所以它的选择性是 50%。 Col3 是在范围 (至少 3-5 页读取)或一个基于非聚集索引(如果 col1 有足够的选择性)。在现实生活中,col1 的分布也有一定的影响,但深入其中会使解释过于复杂。
您可以事后诸葛亮,声称该计划是错误的,但该计划是基于编译时可用数据的最佳成本估算。您可以通过提示来影响它(如您所建议的索引提示,或如 Quassnoi 所建议的那样针对提示进行优化),但随后您的查询可能对您的测试集执行得更好,而对于不同的数据集则更糟,例如@col1 的情况=<the value that matches 500k records>
。您还可以使索引覆盖,从而消除投影列表中需要聚集索引查找的“...”,在这种情况下,非聚集索引始终是比聚集扫描更好的成本匹配。
Kimberley Tripp 有一篇关于这个主题的博客文章,她称之为“index tipping point”,它解释了一个明显完美的候选索引是如何被忽略的:一个不包括投影列表的非聚集索引并且选择性差将被视为比集群扫描更昂贵。
【讨论】:
感谢您的信息和建议。它给了我一些想法。【参考方案8】:如果您的 SELECT 返回的列不在您的索引 SQL 中,我发现扫描聚集索引更有效,而不必执行键查找来查找您请求的其他值。
如果您有 TEXT 列,请尝试将数据类型切换为 VARCHAR(MAX),然后将值包含在非聚集索引中。
【讨论】:
以上是关于SQL Server 不会使用我的索引的主要内容,如果未能解决你的问题,请参考以下文章