我能想到至少三个原因:
- 索引
- 缓存
- 特殊优化(例如 TOP N SORT)
索引
如果在正确索引的数据库上运行,许多类型的查询运行速度会非常快,但如果您在内存中遍历列表,则运行速度会非常慢。例如,在数据库中按 ID(主键)查找几乎是即时的,因为结果存储在高度非常小的 B 树中。要在内存中的列表中找到相同的元素,需要扫描整个列表。
缓存
您的假设是数据库总是命中磁盘。这并非总是如此。数据库将尝试在内存中保存尽可能多的数据,因此当您向它询问数据时,它已经为您准备好了答案。特别是它会在内存中保存常用的索引,并且只在必要时访问磁盘。数据在磁盘和内存中的存储方式也经过精心优化,以减少磁盘寻道和页面缺失。
优化
即使没有索引,数据库仍然知道许多可以加快处理速度的技巧。例如,如果您在 SQL Server 中执行以下操作:
list.OrderBy(x => x.Value).Take(1)
如果列表上有索引,它几乎是即时的,但即使没有索引,它也会使用称为TOP N SORT 的特殊优化,该优化以线性时间运行。检查查询的执行计划以查看是否正在使用此优化。请注意,此优化并未针对 LINQ to Objects 实现。我们可以通过运行这段代码看到这一点:
Random random = new Random();
List<Foo> list = new List<Foo>();
for (int i = 0; i < 10000000; ++i)
{
list.Add(new Foo { Id = random.Next() });
}
DateTime now = DateTime.UtcNow;
Foo smallest = list.OrderBy(foo => foo.Id).First();
Console.WriteLine(DateTime.UtcNow - now);
这段代码需要大约 30 秒的时间来执行,并且随着项目的添加,执行时间比线性增长要慢。用这个替换查询会花费不到一秒钟的时间:
int smallestId = list.Min(foo => foo.Id);
这是因为在 LINQ to objects 中 OrderBy 是使用 O(n log(n)) 算法实现的,但 Min 使用 O(n) 算法。但是,当针对 SQL Server 执行时,这两个查询都会产生相同的 SQL,并且都是线性时间 - O(n)。
因此,在数据库中运行像OrderBy(x => x.Something).Skip(50).Take(10) 这样的分页查询会更快,因为我们付出了更多努力来确保它更快。毕竟,这种查询的速度是数据库的一大卖点。