Я могу придумать, по крайней мере, три причины:
- индексы
- кэширование
- специальные оптимизации (например, TOP N SORT)
Индексы
Существует много типов запросов, которые будут выполняться очень быстро, если они выполняются в базе данных, которая правильно проиндексирована, но очень медленная, если вы просматриваете список в памяти. Например, поиск по идентификатору (первичный ключ) практически мгновенен в базе данных, потому что результаты хранятся в B-дереве с очень небольшой высотой. Чтобы найти тот же элемент в списке в памяти, потребуется сканирование всего списка.
1017 ** * Кэширование 1018 ** * 1019
Вы предполагаете, что база данных всегда попадает на диск. Это не всегда верно. База данных попытается сохранить в памяти как можно больше данных, поэтому, когда вы запрашиваете данные, у вас уже есть готовый ответ. В частности, он будет хранить часто используемые индексы в памяти и попадать на диск только при необходимости. Способ хранения данных на диске и в памяти также тщательно оптимизирован для уменьшения количества обращений к диску и пропусков страниц.
Оптимизация
Даже без индексов база данных все еще знает много хитростей, которые могут ускорить процесс. Например, если вы делаете следующее в 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 к объектам OrderBy
реализован с использованием алгоритма O(n log(n))
, а Min
использует алгоритм O(n)
. Однако при выполнении с SQL Server оба этих запроса будут выдавать один и тот же SQL, и оба будут иметь линейное время - O(n)
.
Таким образом, выполнение пейджингового запроса, например OrderBy(x => x.Something).Skip(50).Take(10)
, выполняется быстрее в базе данных, потому что гораздо больше усилий было уделено тому, чтобы убедиться, что он быстрее. В конце концов, скорость такого рода запросов является основным преимуществом для баз данных.