为什么我的SQL索引会被忽略?

时间:2009-06-09 11:47:51

标签: sql sql-server indexing

我们遇到的问题是,我们的表上的索引被忽略,而SQL Server 2000正在执行表扫描。我们可以使用WITH (INDEX=<index_name>)子句强制使用索引,但不希望这样做。

作为一名开发人员,我在编写T-SQL时非常熟悉SQL Server,但是分析和性能调优不是我的强项。我正在寻找关于为什么会发生这种情况的任何建议和指导。

更新

我应该说我们已经重建了所有索引并更新了索引统计信息。

其中一个罪魁祸首的表定义如下:

CREATE TABLE [tblinvoices]
(
    [CustomerID] [int] NOT NULL,
    [InvoiceNo] [int] NOT NULL,
    [InvoiceDate] [smalldatetime] NOT NULL,
    [InvoiceTotal] [numeric](18, 2) NOT NULL,
    [AmountPaid] [numeric](18, 2) NULL 
        CONSTRAINT [DF_tblinvoices_AmountPaid]  DEFAULT (0),
    [DateEntered] [smalldatetime] NULL 
        CONSTRAINT [DF_tblinvoices_DateEntered]  DEFAULT (getdate()),
    [PaymentRef] [varchar](110),
    [PaymentType] [varchar](10),
    [SyncStatus] [int] NULL,
    [PeriodStart] [smalldatetime] NULL,
    [DateIssued] [smalldatetime] NULL 
        CONSTRAINT [DF_tblinvoices_dateissued]  DEFAULT (getdate()),
    CONSTRAINT [PK_tblinvoices] PRIMARY KEY NONCLUSTERED 
    (
        [InvoiceNo] ASC
    ) ON [PRIMARY]
) ON [PRIMARY]

此表上还有另一个索引(我们希望SQL使用的索引):

CustomerID (Non-Unique, Non-Clustered)

以下查询执行表扫描而不是使用CustomerID索引:

SELECT 
    CustomerID, 
    Sum(InvoiceTotal) AS SumOfInvoiceTotal, 
    Sum(AmountPaid) AS SumOfAmountPaid 
FROM tblInvoices 
WHERE CustomerID = 2112 
GROUP BY customerID

更新

在回答Autocracy's question时,这两个查询都执行表扫描。

更新

回答关于DBCC SHOW_STATISTICS的{​​{3}},数据为:

RANGE_HI_KEY    RANGE_ROWS    EQ_ROWS    DISTINCT_RANGE_ROWS    AVG_RANGE_ROWS
1667            246           454        8                      27.33333
2112            911           3427       16                     56.9375
2133            914           775        16                     57.125

12 个答案:

答案 0 :(得分:5)

  

我们遇到的问题是我们的表上的索引被忽略,而SQL Server 2000正在执行表扫描。

4,302以来Aug 29, 1997天过去了SQL Server天,SkyNet的优化工具尚未演变为Microsoft,但它仍然可以做出错误的决定。

索引提示正是你,一个人,帮助人工智能的方式。

如果您确定收集了统计信息且优化程序仍然存在错误,请继续使用提示。

它们是合法的,正确的,有文档记录的,并且SELECT CustomerID, SUM(InvoiceTotal) AS SumOfInvoiceTotal, SUM(AmountPaid) AS SumOfAmountPaid FROM tblInvoices WHERE CustomerID = 2112 GROUP BY CustomerID 方式支持它们来强制执行您想要的查询计划。

在你的情况下:

KEY LOOKUP

,优化器有两个选择:

  • 使用隐含索引的嵌套循环索引以及InvoiceTotal来获取AmountPaidrows fetched per second
  • 的值
  • 不要使用索引并扫描所有表格行,SELECT 1 - CAST(COUNT(NULLIF(CustomerID, 2112)) AS FLOAT) / COUNT(*) FROM tlbInvoices 更快,但总行数更长。

第一种方法可能比第二种方法更快或更快。

优化器尝试通过查看统计信息来估计哪种方法更快,这使得索引选择性与其他值保持一致。

对于选择性索引,前一种方法更快;对于非选择性的,后者是。

请你运行这个查询:

CustomerID = 2112

<强>更新

由于1,4%仅涵盖行的DBCC SHOW_STATISTICS ([tblinvoices], [CustomerID]) ,因此您应该从使用索引中受益。

现在,您可以运行以下查询:

RANGE_HI_KEY

,找到第三个结果集中的两个邻接行,2112小于2112,并在此处发布行?

更新2:

由于统计数据似乎是正确的,我们只能猜测优化器在这种情况下选择全表扫描的原因。

可能(可能)这是因为这个非常值(RANGE_HI_KEY)出现在3427中,并且优化器发现它非常密集(仅2112 911值仅针对从16682111

的整个范围DBCC SHOW_STATISTICS ([tblinvoices], [CustomerID])

请你再做两件事:

  1. 运行此查询:

    CustomerID

    并发布前两个结果集。

    • 运行此查询:

      SELECT TOP 1 CustomerID,COUNT(*) 来自tblinvoices 客户ID在1668和2111之间

    ,使用原始查询中上述查询中的顶部SELECT CustomerID, SUM(InvoiceTotal) AS SumOfInvoiceTotal, SUM(AmountPaid) AS SumOfAmountPaid FROM tblInvoices WHERE CustomerID = @Top_Customer GROUP BY CustomerID

    {{1}}

    看看它会产生什么计划。

答案 1 :(得分:5)

最好的办法是通过在CustomerID索引中加入 InvoiceTotal AmountPaid 列,使索引成为覆盖索引。 (在SQL 2005中,您可以将它们添加为“包含”列。在SQL 2000中,您必须将它们添加为其他键列。)如果您这样做,我将保证查询优化器会选择你的指数*。

<强>解释: 索引似乎总是有用,但使用(非覆盖)索引会产生隐藏成本,而“书签查找”必须完成以检索任何主表中可能需要的其他列。这种书签查找是一项昂贵的操作,并且是(一种可能)查询优化器可能不会选择使用您的索引的原因。

通过在索引本身中包含所有需要的列,完全避免了这个书签查找,并且优化器不必玩这个小游戏来确定使用索引是否“值得”。

(*)或者我将退还您的StackOverflow积分。只需发送一个带有地址的盖章信封......

编辑:是的,如果你的主键不是聚集索引,那么一定要做到这一点!但即使有了这种改变,使您的CustomerID索引成为覆盖索引也应该将性能提高一个数量级(10倍或更高)!!

答案 2 :(得分:3)

要忽略索引的最常见原因是

  • 所涉及的列不够有选择性(由于'访问'大量行,优化器决定表扫描会更快)

  • SELECT / GROUP BY / ORDER BY中涉及大量列,并且在使用索引后将涉及查找聚簇索引

  • 统计数据已过期(或因大量插入或删除而产生偏差)

您是否正在运行常规索引维护作业? (它在Dev环境中缺失很常见。)

答案 3 :(得分:2)

Kimberly的最新帖子完全涵盖了这个主题:http://www.sqlskills.com/BLOGS/KIMBERLY/post/The-Tipping-Point-Query-Answers.aspx

SQL Server使用基于成本的优化器,如果优化器计算查找索引键然后查找聚簇索引以检索其余列的成本高于成本扫描表,然后它将扫描表。 “小费”实际上是非常低的。

答案 4 :(得分:2)

您是否尝试过将其他列添加到索引中?即InvoiceTotal和AmountPaid。

这个想法是查询将被索引“覆盖”,并且不必再引用该表。

答案 5 :(得分:2)

我会开始测试,看看你是否可以将主键更改为聚簇索引。现在该表被认为是“堆”。如果你不能这样做,那么我也会考虑使用聚集索引创建一个视图,但首先你必须将“AmountPaid”列更改为NOT NULL。它已经默认为零,所以这可能是一个简单的改变。对于视图,我会尝试类似的东西。

SET QUOTED_IDENTIFIER, ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON
GO
SET NUMERIC_ROUNDABORT OFF
GO

IF EXISTS 
  (
         SELECT TABLE_NAME
           FROM INFORMATION_SCHEMA.VIEWS 
          WHERE TABLE_NAME = N'CustomerInvoiceSummary'
  )
           DROP VIEW dbo.CustomerInvoiceSummary
GO

CREATE VIEW dbo.CustomerInvoiceSummary WITH SCHEMABINDING
AS

  SELECT a.CustomerID
       , Sum(a.InvoiceTotal) AS SumOfInvoiceTotal
       , Sum(a.AmountPaid)   AS SumOfAmountPaid 
       , COUNT_BIG(*)                     AS CT
    FROM dbo.tblInvoices a
GROUP BY a.CustomerID

GO
CREATE UNIQUE CLUSTERED INDEX CustomerInvoiceSummary_CLI ON dbo.CustomerInvoiceSummary ( CustomerID )
GO

答案 6 :(得分:2)

我想我刚刚发现它。在我注意到我给你的两个查询预计会导致表扫描之前,我正在阅读发布到你问题的评论,我只想要结果。也就是说,当有人说你没有聚集索引时,它引起了我的兴趣。我详细阅读了你的SQL create语句,并且很惊讶地注意到了这种情况。这就是它没有使用您的CustomerId索引的原因。

您的CustomerId索引引用InvoiceNo的主键。但是,您的主键不是群集的,因此您必须查看该索引以查找该行的实际位置。 SQL服务器不会执行两次非聚集索引查找来查找行。它只是表扫描。

使您的InvoiceNo成为聚集索引。我们可以假设它们通常以递增的方式插入,因此插入成本不会高得多。但是,您的查询成本会低得多。美元到甜甜圈,它会使用你的索引。


编辑:我也喜欢BradC's suggestion。这是一个常见的DBA技巧。然而,就像他说的那样,无论如何都要使主要聚集,因为这是问题的原因。拥有没有聚簇索引的表是非常罕见的。大部分时间没有使用它,这是一个坏主意。也就是说,他的覆盖指数是应该完成的群集改进。

答案 7 :(得分:1)

其他几位人士指出,您的数据库可能需要更新索引统计信息。您可能在数据库中拥有如此高的行百分比,以便顺序读取表会比在磁盘中查找每个表更快。 SQL Server有一个花哨的GUI查询分析器,它会告诉你数据库认为各种活动的成本是多少。你可以打开它,看看它到底在想什么。

如果你能给我们,我们可以给你更坚实的答案:

Select * from tblinvoices;
Select * from tblinvoices where CustomerID = 2112;

使用该查询分析器,并更新您的统计信息。最后一个提示:你可以使用索引提示强制它使用你的索引,如果你确定它在你完成其他所有事情之后只是愚蠢。

答案 8 :(得分:1)

你试过吗

  

exec sp_recompile tblInvoices

...只是为了确保你没有使用缓存糟糕的计划?

答案 9 :(得分:0)

您还可以尝试对查询中涉及的表(或表)执行UPDATE STATISTICS。并不是说我完全理解SQL中的统计信息,但我确实知道这是我们的DBA偶尔会做的事情(每周工作被安排在更大且经常更改的表上更新统计信息)。

SQL Statistics

答案 10 :(得分:0)

试试updating your statistics。这些统计数据是编译器决定是否应该使用索引的基础。它们包含基数和每个表的行数等信息。

例如,如果自从您进行大量批量导入后统计信息尚未更新,则编译器可能仍然认为该表中只有10行,并且不会打扰索引。

答案 11 :(得分:0)

您使用的是“SELECT * FROM ...”吗?这通常会导致扫描。

我们需要架构,索引和示例查询来帮助更多