【发布时间】:2011-06-01 20:00:28
【问题描述】:
在过去的几年里,这个查询已经成为我的克星,因为我从来没有找到优化它的方法。现在我的克星变成了你的克星! :)
考虑下表:
create table Sales (
SaleId int identity(1,1) primary key,
SalesmanId int not null,
Amount smallmoney not null
)
为了论证起见,假设这张表有10^100行(生意兴隆),因此不可能进行表扫描。
现在我们要确定每个销售员最近一次销售的 SaleId。很简单,对吧?这是查询:
select
SalesmanId,
max(SaleId) SaleId
from Sales
group by Sales.SalesmanId
当我们运行这个查询时,查询优化器会进行全表扫描,这是意料之中的,因为它无法知道每个销售员的销售额在表中的哪个位置。因此,让我们通过添加以下索引来帮助它:
create unique nonclustered index IX_Sales on Sales
(
SalesmanId asc,
SaleId asc
)
现在找到最近的值应该是微不足道的(无论如何对于人类来说),因为我们使用索引的第一列的值来识别所有可能的推销员,并使用第二列的最后一个条目来定位每个推销员的最新销售。不幸的是,在这种情况下,查询优化器仍然对整个索引(所有 10^100 行)进行索引查找,所以它需要的时间一样长。
有趣的是,如果我们编写查询来查找给定推销员的最新销售,
select max(SaleId)
from Sales
where SalesmanId = 1
查询优化器在 IX_Sales 上使用索引查找并通过一行 I/O 获取它。即使没有 IX_Sales,它也会进行聚集索引扫描,以某种方式在一行 I/O 中获取它(也许使用表统计信息?)。但是如果我们将其修改为
select max(SaleId)
from Sales
where SalesmanId = 1
group by SalesmanId
或
select max(SaleId)
from Sales
group by SalesmanId
having SalesmanId = 1
我们又回到了对大量行的高行数索引搜索(尽管比完全省略过滤器的情况要少,同样可能是由于统计数据)。
那么...关于如何打败我的克星有什么想法吗?
更新
有些人建议加入可能的 SalesmanId 值表,像这样
select Latest.*
from
(
select
SalesmanId,
max(SaleId) SaleId
from Sales
group by SalesmanId
) Latest
inner join Salesmen on
Salesmen.SalesmanId = Latest.SalesmanId
我测试了这个想法,但查询优化器仍然选择进行全表扫描。
【问题讨论】:
-
你的数据库引擎是什么? (SQL Server、MySQL、PostgreSQL 等)哪个版本?
标签: sql-server-2008 query-optimization