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
数据类型:
- 添加日期 - 小日期时间
- 价格 - 十进制(19,8)
- 数量 - 整数
我在订单 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没有实现。
这是我正在使用的原始查询
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
数据类型:
- 添加日期 - 小日期时间
- 价格 - 十进制(19,8)
- 数量 - 整数
我在订单 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没有实现。