有效的方式来实现分页

我应该使用LINQ的Skip()Take()方法进行分页,还是使用SQL查询来实现我自己的分页?

哪个效率最高? 为什么我会select一个呢?

我正在使用SQL Server 2008,ASP.NET MVC和LINQ。

试图给你一个简短的回答你的疑问,如果你在linq上执行skip(n).take(m)方法(用SQL skip(n).take(m)作为数据库服务器),你的查询将使用Select ROW_NUMBER() Over ...声明,用SQL引擎以某种方式直接分页。

给你一个例子,我有一个名为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] 

这是一个窗口的数据访问(非常酷,顺便说一下,因为从一开始就会返回数据,只要满足条件就可以访问表)。 这将非常类似于:

 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结果执行得更快,因为它将专门使用索引来创build数据访问窗口; 这意味着,如果你需要一些过滤,过滤应该(或必须)在实体列表(创build行的地方),并且还应该创build一些索引以保持良好的性能。

现在,什么更好?

如果你的逻辑有非常稳定的工作stream程,那么实现正确的SQL方式将会非常复杂。 在这种情况下,LINQ将是解决scheme。

如果可以直接将这部分逻辑降低到SQL(在存储过程中),则会更好,因为您可以实现我向您展示的第二个查询(使用索引),并允许SQL生成并存储执行计划查询(改进性能)。

尝试使用

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

在SQL服务器中获取从501到600的行,而不将它们加载到内存中。 请注意,此语法只适用于SQL Server 2012

虽然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值是来自上一页的最后一条logging的相应值。 这使您可以获取“下一个”页面。 如果ORDER BY方向是ASC ,只需使用>代替。

用上面的方法,你不能马上跳到第4页而没有先取得前面的40条logging。 但是,往往你不想跳得那么远。 相反,你会得到一个更快的查询,可能能够在固定时间获取数据,这取决于你的索引。 此外,无论基础数据是否更改(例如,第1页,而第4页),您的页面仍保持“稳定”状态。

例如,这是在Web应用程序中延迟加载更多数据时实现分页的最佳方式。

请注意,“查找方法”也称为密钥集寻呼 。

LinqToSql会自动将.Skip(N1).Take(N2)转换为TSQL语法。 实际上,你在Linq中做的每个“查询”实际上只是在后台为你创build一个SQL查询。 要testing这个,只需在运行应用程序时运行SQL Profiler即可。

skip / take方法对我和其他读者都非常有用。

出于好奇,你有什么types的自我分页查询,你认为比Linq的跳过/采取更有效率?

我们使用包装在Dynamic SQL中的CTE(因为我们的应用程序需要数据服务器端的dynamicsorting)在存储过程中。 如果你愿意,我可以提供一个基本的例子。

我没有机会看看LINQ生成的T / SQL。 有人可以发布样本吗?

我们不使用LINQ或直接访问表,因为我们需要额外的安全层(授予dynamicSQL有点破坏)。

像这样的事情应该做的伎俩。 您可以添加参数的参数值等

 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' 

在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中的所有logging只是对应于那个页面的logging

你可以进一步提高性能,chech这个

 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会得到更好的结果:

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

理由:因为你正在使用CityEntities表上的Where类,在joinMtCity之前会消除许多logging,所以100%肯定会增加很多折叠的性能。

无论如何,由rodrigoelp回答是非常有益的。

谢谢

我会说在SQL存储过程中实现自己的自定义分页逻辑。 这样只返回当前页面logging。 它对我来说效果很好。 我build议看一下自定义分页与SQL Server 。 这是快速和高效的。

 You can implement paging in this simple way by passing 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