SQL 服务器 - 奇怪的索引使用

SQL Server - Weird Index Usage

这是我正在使用的原始查询

SELECT TOP(10) * 
FROM Orders o
WHERE (o.DateAdded >= DATEADD(DAY, - 30, getutcdate())) AND (o.DateAdded <= GETUTCDATE())
ORDER BY o.DateAdded ASC,
o.Price ASC
o.Quantity DESC

数据类型:

我在订单 table 上有一个索引,其中有相同的 3 列且顺序相同,所以当我 运行 这个时,它是完美的。时间 < 0 毫秒,实时查询统计显示它只读取 10 行。厉害了。

但是,一旦我将这一行添加到 WHERE 子句中

AND o.Price BETWEEN convert(decimal(19,8), 0) AND @BuyPrice 

一切都见鬼去吧(不幸的是我需要那句话)。如果只是 o.Price <= @BuyPrice,它的行为也相同。 Live Query Statistics 显示读取的行数约为 30k。它还表明 o.Price 比较没有被用作搜索谓词,我很难理解为什么它不是。我已经验证@BuyPrice 是正确的数据类型,因为我发现了几篇讨论隐式转换问题的文章。起初我以为这是因为我有两个范围:首先是 dateAdded,然后是 Price,但我还有其他查询使用多列索引和多个范围,它们都执行得很好。我完全不明白为什么这个人决定成为一种负担。我试过更改索引中列的顺序,将它们从 ASC 更改为 DESC,但是 nada.

非常感谢任何人告诉我我缺少什么。谢谢

我认为这是由于 TOP 10 不能在 ORDER BY 之前发生。 而这个ORDER BY必须等到结果集就绪。

如果没有您额外的价格范围,TOP 10 可以直接从现有索引中获取。但是添加第二个范围将强制另一个操作首先是运行。

简而言之:

  • 首先,您的过滤器必须获取价格范围行和日期范围行。
  • 对结果集进行排序,并取前 10 行。

您是否尝试过在价格列中添加单独的索引?这应该会加快第一个过滤器的速度。

很多情况下我们无法预测执行计划,但您可以尝试

  • 将按日期范围筛选的中间集写入临时 table,然后从那里继续。您甚至可以在价格列上创建一个索引(取决于预期的行数。可能是最好的选择)。
  • 使用 CTE 定义按日期范围筛选的集合,并使用该集合应用您的价格范围。但是 CTE 与临时 table 不同。最终的执行计划可能和之前一样...
  • 使用两个 CTE 定义两个集合(每个范围一个)并使用 INNER JOIN 作为获得与 WHERE condition1 AND condition2.
  • 相同的方法

优化器不可能同时使用两个范围谓词。

想一想:它从按 DateAdded 排序的索引中的某个位置开始扫描。它现在需要在每个单独的 DateAdded 值内寻找特定的 Price,开始扫描,并在 另一个 Price 处停止,然后跳转到下一个 DateAdded.

这个叫做skip-scanning,只有当第一个predicate的值不是很多的时候才高效,否则就是低效的,所以只有Oracle实现了,SQLServer没有实现。