Эффективный способ реализации подкачки

Должен ли я использовать метод LINQ Skip() и Take() для подкачки или реализовать собственный пейджинг с SQL-запросом?

Что наиболее эффективно? Почему я должен выбирать один за другим?

Я использую SQL Server 2008, ASP.NET MVC и LINQ.

Ответ 1

Пытаясь дать вам краткий ответ на ваши сомнения, если вы выполните методы skip(n).take(m) в linq (с SQL Server 2005/2008 как сервер базы данных), ваш запрос будет использовать оператор Select ROW_NUMBER() Over ..., так как это как-то прямой пейджинг в движке SQL.

Приведя пример, у меня есть таблица db с именем mtcity, и я написал следующий запрос (также работаю с linq для сущностей):

using (DataClasses1DataContext c = new DataClasses1DataContext())
{
    var query = (from MtCity2 c1 in c.MtCity2s
                select c1).Skip(3).Take(3);
    //Doing something with the query.
}

Итоговый запрос будет выглядеть следующим образом:

SELECT [t1].[CodCity], 
    [t1].[CodCountry], 
    [t1].[CodRegion], 
    [t1].[Name],  
    [t1].[Code]
FROM (
    SELECT ROW_NUMBER() OVER (
        ORDER BY [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]) AS [ROW_NUMBER], 
        [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]
    FROM [dbo].[MtCity] AS [t0]
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER]

Что такое доступ к окну доступа (довольно круто, btw cuz будет возвращать данные с самого начала и будет обращаться к таблице, пока выполняются условия). Это будет очень похоже на:

With CityEntities As 
(
    Select ROW_NUMBER() Over (Order By CodCity) As Row,
        CodCity //here is only accessed by the Index as CodCity is the primary
    From dbo.mtcity
)
Select [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]
From CityEntities c
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity
Where c.Row Between @p0 + 1 AND @p0 + @p1
Order By c.Row Asc

За исключением того, что этот второй запрос будет выполняться быстрее, чем результат linq, потому что он будет использовать исключительно индекс для создания окна доступа к данным; это означает, что если вам нужна фильтрация, фильтрация должна быть (или должна быть) в списке Entity (где создается строка), а некоторые индексы должны быть созданы, чтобы поддерживать хорошую производительность.

Теперь, что лучше?

Если у вас в вашей логике довольно сложный рабочий процесс, реализация правильного пути SQL будет сложной. В этом случае LINQ будет решением.

Если вы можете опустить эту часть логики непосредственно на SQL (в хранимой процедуре), это будет еще лучше, потому что вы можете реализовать второй запрос, который я показал вам (используя индексы), и разрешить SQL генерировать и хранить выполнение План запроса (повышение производительности).

Ответ 2

Попробуйте использовать

FROM [TableX]
ORDER BY [FieldX]
OFFSET 500 ROWS
FETCH NEXT 100 ROWS ONLY

чтобы получить строки с 501 до 600 на SQL-сервере, не загружая их в память. Обратите внимание, что этот синтаксис стал доступен только с SQL Server 2012

Ответ 3

В то время как LINQ-to-SQL генерирует предложение OFFSET (возможно, эмулируется с использованием ROW_NUMBER() OVER() как упомянули другие), существует совершенно иное, более быстрый способ выполнения подкачки в SQL. Это часто называют "методом поиска", как описано в этот пост в блоге здесь.

SELECT TOP 10 first_name, last_name, score
FROM players
WHERE (score < @previousScore)
   OR (score = @previousScore AND player_id < @previousPlayerId)
ORDER BY score DESC, player_id DESC

Значения @previousScore и @previousPlayerId являются соответствующими значениями последней записи на предыдущей странице. Это позволяет вам получать "следующую" страницу. Если направление ORDER BY ASC, просто используйте >.

С помощью вышеуказанного метода вы не можете сразу перейти на страницу 4, не предварительно извлек предыдущие 40 записей. Но часто вы не хотите так далеко прыгать. Вместо этого вы получаете гораздо более быстрый запрос, который мог бы получать данные в постоянное время, в зависимости от вашей индексации. Кроме того, ваши страницы остаются "стабильными", независимо от того, изменяются ли базовые данные (например, на странице 1, когда вы находитесь на странице 4).

Это лучший способ реализовать пейджинг при ленивой загрузке большего количества данных в веб-приложениях, например.

Обратите внимание, что "метод поиска" также называется подкачкой набора ключей.

Ответ 4

LinqToSql автоматически преобразует .Skip(N1).Take(N2) в синтаксис TSQL для вас. Фактически, каждый "запрос", который вы выполняете в Linq, на самом деле просто создает SQL-запрос для вас в фоновом режиме. Чтобы проверить это, просто запустите SQL Profiler во время работы вашего приложения.

Методология пропусков/взятий очень хорошо работала для меня, а другие - из того, что я читал.

Из любопытства, какой у вас запрос самообучения, который, по вашему мнению, более эффективен, чем Linq skip/take?

Ответ 5

Мы используем CTE, завернутый в Dynamic SQL (потому что наше приложение требует динамической сортировки на стороне сервера данных) в хранимой процедуре. Я могу предоставить базовый пример, если вы хотите.

У меня не было возможности посмотреть на T/SQL, который производит LINQ. Может ли кто-нибудь отправить образец?

Мы не используем LINQ или прямой доступ к таблицам, так как нам нужен дополнительный уровень безопасности (при условии, что динамический SQL разбивается на несколько).

Что-то вроде этого должно сделать трюк. Вы можете добавить параметризованные значения для параметров и т.д.

exec sp_executesql 'WITH MyCTE AS (
    SELECT TOP (10) ROW_NUMBER () OVER ' + @SortingColumn + ' as RowID, Col1, Col2
    FROM MyTable
    WHERE Col4 = ''Something''
)
SELECT *
FROM MyCTE
WHERE RowID BETWEEN 10 and 20'

Ответ 6

В SQL Server 2008:

DECLARE @PAGE INTEGER = 2
DECLARE @TAKE INTEGER = 50

SELECT [t1].*
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[COLUMNORDER] DESC) AS [ROW_NUMBER], [t0].*
    FROM [dbo].[TABLA] AS [t0]
    WHERE ([t0].[COLUMNS_CONDITIONS] = 1)
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN ((@PAGE*@TAKE) - (@TAKE-1)) AND (@PAGE*@TAKE)
ORDER BY [t1].[ROW_NUMBER]

В t0 все записи В t1 указаны только те, которые соответствуют этой странице

Ответ 7

вы можете еще больше повысить производительность, chech this

From CityEntities c
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity
Where c.Row Between @p0 + 1 AND @p0 + @p1
Order By c.Row Asc

если вы будете использовать это так, это даст лучший результат:

From   dbo.MtCity  t0
   Inner Join  CityEntities c on c.CodCity = t0.CodCity

причина: потому что вы используете класс where в таблице CityEntities, который устранит многие записи, прежде чем присоединяться к MtCity, поэтому на 100% уверен, что это увеличит производительность во много раз...

В любом случае ответ от rodrigoelp действительно полезен.

Спасибо

Ответ 8

Я бы сказал, чтобы реализовать собственную логику подкачки в хранимой процедуре sql. Так что возвращаются только текущие записи страниц. Это работает очень хорошо для меня. Я бы рекомендовал взглянуть на Пользовательский пейджинг с SQL Server. Это быстро и эффективно.

Ответ 9

Вы можете реализовать подкачку этим простым способом, передав PageIndex

Declare @PageIndex INT = 1
Declare  @PageSize INT = 20

Select ROW_NUMBER() OVER ( ORDER BY Products.Name ASC )  AS RowNumber,
    Products.ID,
    Products.Name
into #Result 
From Products

SELECT @RecordCount = COUNT(*) FROM #Results 

SELECT * 
FROM #Results
WHERE RowNumber
BETWEEN
    (@PageIndex -1) * @PageSize + 1 
    AND
    (((@PageIndex -1) * @PageSize + 1) + @PageSize) - 1

Ответ 10

В 2008 году мы не можем использовать Skip(). Take()

Путь:

var MinPageRank = (PageNumber - 1) * NumInPage + 1
var MaxPageRank = PageNumber * NumInPage

var visit = Visita.FromSql($"SELECT * FROM (SELECT [RANK] = ROW_NUMBER() OVER (ORDER BY Hora DESC),* FROM Visita WHERE ) A WHERE A.[RANK] BETWEEN {MinPageRank} AND {MaxPageRank}").ToList();