数据分页的一些想法

  • 列表内容

    最近和DBA讨论了一下项目中经常用到数据分页的问题,收益颇多,这里总结下自己关于分页的一些想法和建议。
    目前了解到的分页方法有三种,下面一一介绍:

  • 方法一

之前采用的是使用ROW_NUMBER函数为数据添加序列号,之后按照这个序列号,取自己需要的一段。
SQL如下:

DECLARE @start AS INT, @end AS INT, @TotalItemCount AS INT;

SET @start = 1 * 1;

SET @end = 1 * 10;

SELECT @TotalItemCount = COUNT(1)
FROM   [PkgInputDB].[dbo].Prd_ResourceSalesPriceDetail AS R WITH (NOLOCK)
WHERE  1 = 1;

WITH   result
AS     (SELECT ROW_NUMBER() OVER (ORDER BY ID ASC) AS RowNumber,
               R.ID,
               R.SalesPriceID,
               R.EffectDate,
               R.ExpireDate,
               R.SalesPrice,
               R.Active,
               R.InUse
        FROM   [PkgInputDB].[dbo].Prd_ResourceSalesPriceDetail AS R WITH (NOLOCK)
        WHERE  1 = 1)
SELECT @TotalItemCount AS TotalItemCount,
       *
FROM   result
WHERE  RowNumber BETWEEN @start AND @end;

其执行计划:
这里写图片描述
在生产上执行效果:
这里写图片描述
这里写图片描述

  • 方法二

是微软在SQL Server 2012?中推出的一种方法,采用OFFSET FETCH 语句。
SQL如下:

DECLARE @start AS INT, @end AS INT, @TotalItemCount AS INT;

SET @start = (@PageIndex - 1) * @PageSize + 1;

SET @end = @PageIndex * @PageSize;

SELECT @TotalItemCount = COUNT(1)
FROM   [PkgInputDB].[dbo].Prd_ResourceSalesPriceDetail AS R WITH (NOLOCK)
WHERE  1 = 1;

WITH     result
AS       (SELECT R.ID,
                 R.SalesPriceID,
                 R.EffectDate,
                 R.ExpireDate,
                 R.SalesPrice,
                 R.Active,
                 R.InUse
          FROM   [PkgInputDB].[dbo].Prd_ResourceSalesPriceDetail AS R WITH (NOLOCK)
          WHERE  1 = 1)

SELECT   @TotalItemCount AS TotalItemCount,
         *
FROM     result
ORDER BY ID
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;

执行计划如下:

这里写图片描述

可以看出新推出的方法执行过程比第一种少很多。
生产执行效果:
这里写图片描述
这里写图片描述

  • 方法三

DBA同学推荐的做法是:按照索引列进行排序,并在where 条件中增加 >min值 然后查询top N N是每页显示数据量,翻页时将上一次查询的最大值 作为这次查询的最小值。
SQL语句如下:

DECLARE @start AS INT, @end AS INT, @TotalItemCount AS INT;

SET @end = 10;

SELECT @TotalItemCount = COUNT(1)
FROM   [PkgInputDB].[dbo].Prd_ResourceSalesPriceDetail AS R WITH (NOLOCK)
WHERE  1 = 1;

WITH     result
AS       (SELECT R.ID,
                 R.SalesPriceID,
                 R.EffectDate,
                 R.ExpireDate,
                 R.SalesPrice,
                 R.Active,
                 R.InUse
          FROM   [PkgInputDB].[dbo].Prd_ResourceSalesPriceDetail AS R WITH (NOLOCK)
          WHERE  1 = 1
                 AND ID > 0)
SELECT   *
FROM     result
ORDER BY ID ASC
OFFSET 0 ROWS FETCH NEXT @end ROWS ONLY;

执行计划如下:

这里写图片描述

生产上执行效果:
这里写图片描述
这里写图片描述

  • 效果对比

从执行计划上看只有第一种过程略微复杂,但对性能似乎并没有什么影响,不过考虑到过程越复杂,损耗可能就越大,故采用2,3比较合适;我们再测试下,分页数变多时,各sql执行情况
第二种分页方法:
第10000页时:

这里写图片描述

第10万时:

这里写图片描述

第100万页时:
SQL超时,无结果!

第三种分页方法:
第10000页时:

这里写图片描述

第10万页时:

这里写图片描述

可以看出随着分页数的增加,第二种分页方法耗时会越来越大,而第三种方法则不受影响。

  • 结语

第一种在性能没有优势,且写法复杂可以直接淘汰。这里主要讨论第二种和第三种分页方式的优缺点,以及适用的场景。
当需求需要获取记录总数(多数情况下其实都可以不用返回总数的,只要和产品经理动之以情晓之以理,一般都会取消获取总数的)时,分页耗时相对获取总数耗时可以忽略不计,故这里只讨论不需要获取记录总数的情况。
因为第二种分页方法会随着分页数增加,耗时增大,故无特殊要求时采用第三种分页比较好。
当然如果需要跳页时,因为第三种分页是基于上次一次分页结果,所以不好实现,这种情况下可以采用第二种分页方法。
排序要求比较复杂,且无固定顺序时,也只能采用第二种分页方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值