【问题标题】:Why doesn't this query use the proper index?为什么这个查询不使用正确的索引?
【发布时间】:2011-08-24 07:47:47
【问题描述】:

表定义:

CREATE TABLE [dbo].[AllErrors](
  [ID] [int] IDENTITY(1,1) NOT NULL,
  [DomainLogin] [nvarchar](50) NULL,
  [ExceptionDate] [datetime] NULL,
  [ExceptionDescr] [nvarchar](max) NULL,
  [MarketName] [nvarchar](50) NULL,
  [Version] [nvarchar](50) NULL,
  CONSTRAINT [PK_AllErrors] PRIMARY KEY CLUSTERED ([ID] ASC)
)

-- Add an index on the date
CREATE NONCLUSTERED INDEX [IX_ExceptionDate] ON [dbo].[AllErrors] ([ExceptionDate] ASC)

我运行这个查询:

declare @yesterday datetime
select @yesterday = getdate() - 1

SELECT * INTO #yst
from AllErrors 
where ExceptionDate between @yesterday and @yesterday + 1

此代码不使用我的IX_ExceptionDate(从执行计划中收集)。它对主键索引进行聚集扫描。但是,下面的代码确实使用了IX_ExceptionDate 索引:

SELECT * INTO #yst
from AllErrors 
where ExceptionDate between @yesterday and @yesterday + 1
  AND ExceptionDate = ExceptionDate

这是为什么?

编辑:添加了视觉执行计划。

编辑:下面的文本执行计划。

查询 1:

|--表格插入(OBJECT:([#yst]), SET:([#yst].[ID] = [Expr1006],[#yst].[DomainLogin] = [MarketStats].[dbo] .[AllErrors].[DomainLogin],[#yst].[ExceptionDate] = [MarketStats].[dbo].[AllErrors].[ExceptionDate],[#yst].[ExceptionDescr] = [MarketStats].[dbo] .[AllErrors].[ExceptionDescr],[#yst].[MarketName] = [MarketStats].[dbo].[AllErrors].[MarketName],[#yst].[Version] = [MarketStats].[dbo] .[AllErrors].[Version])) |--顶部(ROWCOUNT est 0) |--计算标量(DEFINE:([Expr1006]=setidentity([MarketStats].[dbo].[AllErrors].[ID],(-7),(0),N'#yst'))) |-- 聚集索引扫描(OBJECT:([MarketStats].[dbo].[AllErrors].[PK_AllErrors]), WHERE:([MarketStats].[dbo].[AllErrors].[ExceptionDate]>=[@yesterday ] AND [MarketStats].[dbo].[AllErrors].[ExceptionDate]

查询 2:

|--表格插入(OBJECT:([#yst]), SET:([#yst].[ID] = [Expr1006],[#yst].[DomainLogin] = [MarketStats].[dbo] .[AllErrors].[DomainLogin],[#yst].[ExceptionDate] = [MarketStats].[dbo].[AllErrors].[ExceptionDate],[#yst].[ExceptionDescr] = [MarketStats].[dbo] .[AllErrors].[ExceptionDescr],[#yst].[MarketName] = [MarketStats].[dbo].[AllErrors].[MarketName],[#yst].[Version] = [MarketStats].[dbo] .[AllErrors].[Version])) |--顶部(ROWCOUNT est 0) |--计算标量(DEFINE:([Expr1006]=setidentity([MarketStats].[dbo].[AllErrors].[ID],(-7),(0),N'#yst'))) |-- 嵌套循环(内连接,外引用:([MarketStats].[dbo].[AllErrors].[ID],[Expr1008])优化无序预取) |--Index Seek(OBJECT:([MarketStats].[dbo].[AllErrors].[IX_ExceptionDate]), SEEK:([MarketStats].[dbo].[AllErrors].[ExceptionDate] >= [@yesterday] AND [MarketStats].[dbo].[AllErrors].[ExceptionDate]

【问题讨论】:

  • 可以发一下执行计划吗?
  • 你能发布计划吗,我得到的几乎一样|--Clustered Index Scan(OBJECT:([aspnetdb].[dbo].[AllErrors].[PK_AllErrors]), WHERE:([aspnetdb].[dbo].[AllErrors].[ExceptionDate]>=[@yesterday] AND [aspnetdb].[dbo].[AllErrors].[ExceptionDate]<=[@yesterday]+'1900-01-02 00:00:00.000'))
  • |--Clustered Index Scan(OBJECT:([aspnetdb].[dbo].[AllErrors].[PK_AllErrors]), WHERE:([aspnetdb].[dbo].[AllErrors].[ExceptionDate]>=[@yesterday] AND [aspnetdb].[dbo].[AllErrors].[ExceptionDate]<=[@yesterday]+'1900-01-02 00:00:00.000' AND [aspnetdb].[dbo].[AllErrors].[ExceptionDate]=[aspnetdb].[dbo].[AllErrors].[ExceptionDate]))
  • 编译查询时不知道变量的值是什么。你可以试试OPTION (RECOMPILE)。我认为no-op and 子句使它相信查询将更具选择性(通常它假设 10% 将在没有统计信息的情况下匹配= - 奇怪的是它没有认识到这实际上与ExceptionDate is not null 虽然这已经得到BETWEEN 的保证)。两个计划中的估计行数是多少?
  • @Angry - 正如我上面所说,它假设 10% 用于相等谓词,因此所有加起来!它假设中间将给出88234.8 行,然后清楚地看到AND= 并假设这会将结果的大小减小到10%,而不考虑到equals 与列本身相对的事实!

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


【解决方案1】:

它不知道编译查询时变量的值是什么。你可以试试OPTION (RECOMPILE)

我认为在查询中添加AND 子句(即使从逻辑上讲它根本没有选择性)必须误导优化器以更大的选择性估计查询,从而为您提供您想要的计划!

您在 cmets 中说没有ExceptionDate = ExceptionDate 的版本估计在88234.8 行和带有8823.48 的版本

通常在没有可用统计信息的情况下,SQL Server 会根据谓词中比较运算符的类型回退到启发式算法。

它假设 > 谓词将返回 30% 的行,而 = 谓词将返回 10% 的行,所以看起来它只是将其直接应用于第一个结果估计。有趣的是,它没有考虑到 equals 是针对列本身的事实!

参考Best Practices for Managing Statistics - Avoid use of local variables in queries

【讨论】:

    【解决方案2】:

    简短回答:由于“SELECT *”,您的查询会命中聚集索引:键查找操作比聚集索引扫描成本高得多。

    查看由

    产生的不同查询计划
    declare @yesterday datetime
    select @yesterday = getdate() - 1
    
    SELECT * INTO dbo.#yst
    from AllErrors WITH (INDEX = IX_ExceptionDate)
    where ExceptionDate between @yesterday and @yesterday + 1
    

    declare @yesterday datetime
    select @yesterday = getdate() - 1
    
    SELECT * INTO dbo.#yst
    from AllErrors
    where ExceptionDate between @yesterday and @yesterday + 1
    

    declare @yesterday datetime
    select @yesterday = getdate() - 1
    
    SELECT ExceptionDate INTO dbo.#yst
    from AllErrors
    where ExceptionDate between @yesterday and @yesterday + 1
    

    【讨论】:

    • 如果您需要所有数据,请从表格中获取。一旦你获得了相当数量的行,你的所有成本都应该在插入中。
    • 这是个好主意,但结果是一样的(我确实需要表中的所有列,而不仅仅是 ExceptionDate)。
    • 使用 * 不是好的做法,恕我直言。更好地枚举字段
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-05
    • 1970-01-01
    • 1970-01-01
    • 2021-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多