【问题标题】:Why is SQL Server choosing the "wrong" Index?为什么 SQL Server 选择“错误”索引?
【发布时间】:2021-04-19 20:01:09
【问题描述】:

我有一个包含大约 2 亿条记录的事务表,一个主键聚集在 Id 和 2 个索引上:

  • IX_SiloId_ChangedTime_IncludeTime
  • IX_SiloId_Time_IncludeContent

在执行实际查询以更新统计信息之前,我会运行这两条语句

Update STATISTICS dbo.[Transaction] IX_SiloId_ChangedTime_IncludeTime WITH FULLSCAN
Update STATISTICS dbo.[Transaction] IX_SiloId_Time_IncludeContent WITH FULLSCAN

这是我的查询:

DECLARE @Query SiloTimeQueryTableType -- (SiloId, Time) with primary key clustered on SiloId
INSERT INTO @Query VALUES 
(1, '2020-12-31'), -- 1000 total values, though it's still the same problem with just one

SELECT  t.*
FROM    [Transaction] t
INNER JOIN @Query q
    ON t.SiloId = q.SiloId
WHERE 
    t.Time >= q.Time

现在无论出于何种原因 Sql Server 选择了IX_SiloId_ChangedTime_IncludeTime,都会发生什么。然后它需要永远。如果我使用WITH (INDEX(IX_SiloId_Time_IncludeContent)),我会立即得到结果。

正确的索引在这里很明显,但是 SQL Server 选择了一个甚至没有在 Time 上建立索引的索引。

我无法理解这种行为,但从我阅读的内容来看,最好避免对索引的提示,尽管我在制作这个索引时考虑到了这个查询。

所以问题是:我该怎么做才能弄清楚为什么 SQL Server 更喜欢“错误”的索引,即使存在更好的索引并且我只是运行完整的统计更新?

我创建了一个临时表,因为许多人认为 TVP 失败,但结果是一样的:

CREATE TABLE #Query
(
    SiloId bigint NOT NULL PRIMARY KEY CLUSTERED,
    Time datetime2(7) NOT NULL
)

执行计划:

https://www.brentozar.com/pastetheplan/?id=rJOt3G00P

https://www.brentozar.com/pastetheplan/?id=ByFshGAAP(这个是live,因为太长了)

指数:

CREATE NONCLUSTERED INDEX [IX_SiloId_Time_IncludeContent] ON [dbo].[Transaction]
(
    [SiloId] ASC,
    [Time] ASC
)
INCLUDE([SiloContent]) WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_SiloId_ChangedTime_IncludeTime] ON [dbo].[Transaction]
(
    [SiloId] ASC,
    [ChangedTime] ASC
)
INCLUDE([Time]) WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO

【问题讨论】:

  • 您为什么要向 SO 和 DBA 发布相同的问题?我看到你删除了你之前关于讨论使用 tvp / table 变量的同一主题的问题。
  • 表变量没有统计信息,这可能是SQL Server选择错误索引的原因。我建议你改用临时表,看看在这种情况下 SQL Server Server 是否选择了正确的索引
  • @SMor 因为这是 2 个不同的网站,显然有不同的人在回答?
  • @JesúsLópez 我已经读过这个,我实际上已经尝试过使用临时表。结果是一样的。似乎实际上在打开实时统计信息后它会进行聚集索引扫描......
  • 能否请您发布执行计划?您可能想使用brentozar.com/pastetheplan。请显示索引和表定义。

标签: sql sql-server indexing sql-execution-plan


【解决方案1】:

无论出于何种原因 Sql Server 选择 IX_SiloId_ChangedTime_IncludeTime

这不是执行计划所说的。未指定索引提示时,SQL Server 选择 PK_Transaction 聚集索引。

我很清楚为什么 SQL Server 在查看执行计划时选择PK_Transaction 而不是IX_SiloId_Time_IncludeContent。原因是基数估计不佳。两个执行计划都显示 SQL Server 估计连接操作产生 2.5182.000 行,但它实际上产生 4.155 行。如果 SQL Server 选择IX_SiloId_Time_IncludeContent,那么它估计需要执行 2.5182.000 次键查找。使用IX_SiloId_Time_IncludeContent 索引进行 2.5182.000 键查找时,该计划比使用散列匹配和聚集索引扫描的计划更昂贵。如果 SQL Server 能够更好地估计,它会选择IX_SiloId_Time_IncludeContent,因为只有 4.155 个键查找,该计划的成本要低得多。

那么,你能做什么?我认为有两个选择:

  • 包括索引提示。索引提示的存在是有原因的。糟糕的基数估计是包含提示的一个很好的理由。
  • 尝试使用第一个执行计划建议的覆盖索引。使用覆盖索引,不需要键查找。所以很有可能是 SQL Server 选择了覆盖索引。

【讨论】:

  • 非常感谢您提供了一个很好的答案,这有助于了解正在发生的事情。覆盖索引确实需要整个表,虽然我可以做到这一点,但这似乎有点矫枉过正。我真的不知道为什么估计值相差这么多......,如果能弄清楚这一点也很好:) 我确实在那之前运行了更新统计数据
  • 关于错误的索引:当我只查询 1 个元素时,它选择了错误的索引,但它花了 1000 的时间,而且我不知道我可以看到“实时”计划,即这就是混乱的原因,对此感到抱歉:)
  • @IlyaChernomordik 获得精确的估计非常非常困难。它基于基于样本的统计数据。一些条件可以导致良好的估计。但是t.Time >= q.Time这个条件很难得到很好的估计。
  • 我在临时表上添加了索引,不幸的是它没有帮助,但有趣的是,选项 FORCE_LEGACY_CARDINALITY_ESTIMATION 有帮助并且估计是正确的!真的不知道我应该用它做什么,在查询中有这样的东西很奇怪
  • @IlyaChernomordik,是的,遗留基数估计更乐观(估计行更少),但平均而言更差。这并不意味着在某些情况下遗留基数估计会更好
猜你喜欢
  • 2013-01-03
  • 2011-05-03
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 2012-01-18
  • 2013-05-18
相关资源
最近更新 更多