Проблема с предложениями в этой теме и в других разделах сети состоит в том, что все предлагаемые решения выполняются за линейное время по отношению к количеству записей. Например, рассмотрим запрос, подобный следующему.
select *
from
(
select
Row_Number() over (order by ClusteredIndexField) as RowNumber,
*
from MyTable
) as PagedTable
where RowNumber between @LowestRowNumber and @HighestRowNumber;
При получении страницы 1 запрос занимает 0,577 секунды. Однако при получении страницы 15 619 этот же запрос занимает более 2 минут 55 секунд.
Мы можем значительно улучшить это, создав номер записи, индексную перекрестную таблицу, как показано в следующем запросе. Кросс-таблица называется PagedTable и является непостоянной.
select *
from
(
select
Row_Number() over (order by Field1 asc, Field2 asc, Field3 asc) as RowNumber,
ClusteredIndexField
from MyTable
) as PagedTable
left join MyTable on MyTable.ClusteredIndexField = PagedTable.ClusteredIndexField
where RowNumber between @LowestRowNumber and @HighestRowNumber;
Как и в предыдущем примере, я проверил это на очень широкой таблице с 780 928 записями. Я использовал размер страницы 50, что привело к 15 619 страницам.
Общее время, затрачиваемое на страницу 1 (первую страницу), составляет 0,413 секунды. Общее время, затрачиваемое на страницу 15 619 (последнюю страницу), составляет 0,987 секунды, что в два раза больше, чем на странице 1. Это время измерялось с помощью SQL Server Profiler, а СУБД была SQL Server 2008 R2.
Это решение подходит для любого случая, когда вы сортируете свою таблицу по индексу. Индекс не должен быть кластеризованным или простым. В моем случае индекс состоял из трех полей: varchar (50) asc, varchar (15) asc, числовой (19,0) asc. То, что производительность была превосходной, несмотря на громоздкий индекс, еще раз демонстрирует, что этот подход работает.
Однако очень важно, чтобы предложение order by в оконной функции Row_Number соответствовало индексу. В противном случае производительность снизится до того же уровня, что и в первом примере.
Этот подход все еще требует линейной операции для генерации непостоянной перекрестной таблицы, но, поскольку это просто индекс с добавленным номером строки, это происходит очень быстро. В моем случае это заняло 0,347 секунды, но в моем случае были копии, которые нужно было скопировать. Один числовой индекс занял бы гораздо меньше времени.
Для всех практических целей этот дизайн уменьшает масштабирование подкачки на стороне сервера от линейной операции до логарифмической операции, позволяющей масштабировать большие таблицы. Ниже приведено полное решение.
-- For a sproc, make these your input parameters
declare
@PageSize int = 50,
@Page int = 15619;
-- For a sproc, make these your output parameters
declare @RecordCount int = (select count(*) from MyTable);
declare @PageCount int = ceiling(convert(float, @RecordCount) / @PageSize);
declare @Offset int = (@Page - 1) * @PageSize;
declare @LowestRowNumber int = @Offset;
declare @HighestRowNumber int = @Offset + @PageSize - 1;
select
@RecordCount as RecordCount,
@PageCount as PageCount,
@Offset as Offset,
@LowestRowNumber as LowestRowNumber,
@HighestRowNumber as HighestRowNumber;
select *
from
(
select
Row_Number() over (order by Field1 asc, Field2 asc, Field3 asc) as RowNumber,
ClusteredIndexField
from MyTable
) as PagedTable
left join MyTable on MyTable.ClusteredIndexField = PagedTable.ClusteredIndexField
where RowNumber between @LowestRowNumber and @HighestRowNumber;