我的asp.net经验之谈

asp.net技术相关

博客园 首页 管理
  100 Posts :: 0 Stories :: 1283 Comments :: 44 Trackbacks

上两篇随笔:
我的分页控件(未完,待续)——控件件介绍及思路

我自己写的一个分页控件(源码和演示代码)PostBack分页版 for vs2003、SQL Server

关于分页的误区
    误区1:分页的时候,只有使用存储过程,效率才高。
    误区2:忽略了索引的作用。

上两篇好像介绍的不太详细,这里详细说明一下分页控件里使用的分页算法,也就是SQL语句。

分页一般分为四种情况

1、单字段排序,排序字段没有重复值。
2、单字段排序,排序字段有重复值。
3、多字段排序,最后一个排序字段没有重复值。
4、多字段排序,最后一个排序字段有重复值。

其中第2、4 情况都可以再加一个排序字段(比如说主键),就可以转换成第三种情况。

所以分页针对1、3两种情况设置了两种分页算法。

1、单字段排序,排序字段没有重复值。

    公式:

 1declare @col int
 2
 3select top {PageSize * (PageIndex-1)+1@col = [排序字段] 
 4from [表名|视图名] 
 5[ where 查询条件 ] 
 6order by [排序字段] asc|desc
 7
 8select top PageSize 需要显示的字段 
 9from [表名|视图名] 
10where [排序字段] >= @col
11[ and 查询条件 ]
12order by [排序字段] asc|desc

    以NorthWind 数据库里的 Products 表为例,假设一页显示10条数据,CategoryID = 3 为查询条件,按照ProductID 倒序,如果想显示第二页的数据,那么SQL语句就是
    
declare @col int

select top 11 @col = ProductID from Products where CategoryID = 3 order by ProductID desc    

select top 10 * from Products where ProductID >= @col and CategoryID = 3 order by ProductID desc    

   说明:
    第一行的定义,要根据字段类型来修改,看是比较麻烦,但是这个麻烦交给分页控件就可以了,使用者,只要设置分页控件的属性就可以了。
    第五行和第十一行,如果需要加查询条件的话就可以在这里添加。

    第三行是一个“定位”,这个可以算是SQL Server 所特有的吧,也是SQL Server 很宽容的地方。以Products 表的例子,执行完第一条select 语句之后, @col 里面记录的是 在CategoryID = 3 的记录里面,按照ProductID 倒序,排行在11位的记录的值。
    
    第一个select 语句定位以后,第二个select 语句就可以根据这个“位置”继续向下查找数据了。

    虽然例子里面使用了ProductID(主键)来排序,但是并不是说这个算法只能用主键来排序,哪个字段都可以,但是要符合第一种情况,就是“只有一个排序字段,且排序字段里的记录没有重复值”!


3、多字段排序,最后一个排序字段没有重复值。

    如果 Products 表想要用 UnitPrice 字段来排序怎么办呢?上面的算法是不适合的,我们需要使用另一种算法,这个和颠颠倒倒法有些类似,但是我做了一些优化。

    公式:
select [需要显示的字段] from [表名|视图名] where [主键字段] in 

    ( 
select top PageSize [主键字段] from 
        (
select top {PageSize * PageIndex} [主键字段] , [排序字段] from     --有几个排序字段就写几个字段

            
[表名|视图名] 
            
[ where 查询条件 ] 
            
order by 
            
                
[排序字段1] asc|desc ,
                
[排序字段2] desc|asc
                
                
[主键字段] asc|desc     
        ) 
as aa     
        
order by     
            
[排序字段1] desc|asc,        --如果上面是倒序,那么这里就是正序,下同
                [排序字段2] asc|desc ,
                    
                    
[主键字段] desc|asc 

    ) 

order by 
    
[排序字段1] asc|desc,    --如果上面是倒序,那么这里就是正序,所谓颠颠倒倒嘛。
    [排序字段2] desc|asc,
    
    
[主键字段] asc|desc

    以NorthWind 数据库里的 Products 表为例,假设一页显示10条数据,CategoryID = 3 为查询条件,按照UnitPrice 倒序,由于UnitPrice 字段可能有重复值,所以加上一个排序字段——ProductID ,即按照 UnitPrice  desc,ProductID  来排序。 如果想显示第二页的数据,那么SQL语句就是
select * from Products where ProductID in 
    ( 
select top 10 ProductID from 
        (
select top 20 ProductID , UnitPrice from     
            Products 
            
where CategoryID = 3
            
order by 
                UnitPrice 
desc ,
                ProductID      
        ) 
as aa     
        
order by     
            UnitPrice 
asc,        --如果上面是倒序,那么这里就是正序,下同
                ProductID desc 
    ) 
order by 
    UnitPrice 
desc,    --如果上面是倒序,那么这里就是正序,所谓颠颠倒倒嘛。
    ProductID 

    说明:
    1、这里查询条件加一次就可以了。
    2、是不是看 asc|desc 倒来倒去的有点晕,恩,这就对了,颠颠倒倒嘛。
    3、最主要的就是第三个select 语句,他要取从第一条数据到要显示的页的数据,可见越是后面的记录,top n 就会越大,所以这里提取的数据就要做一个精简,只写排序需要的字段(主键字段和排序字段)。
    4、第二个select 语句是去掉前面不需要的页里的数据,只保留要显示的页号里的数据。
    5、第一个select 语句,用主键字段 in () 的方式提取其他需要的字段。
    6、这种分页算法有一个小的bug,就是显示最后一页数据的时候,会多出来几条记录,不过这个bug已经在分页控件里面修正了,最后一页的分页算法,采用特殊的select语句。
    7、效率,设置好索引,效率是没有问题的,上一篇随笔已经测试过了。
    8、这种算法有一个“侵入性”,就是要求表必须有主键,而且不能是联合主键,引为要用 in 的方式查询数据。但是并没有要求主键自身必须能够排序。

测试效果
记录数:2523136
一页显示5条记录。

//分页算法1 单字段排序,且排序字段是聚集索引。
   //1000 页以内 15毫秒
   //10000页以内 30毫秒
   //50000页以内 100多毫秒
   //100000页以内 200多毫秒
   //最后几页 第一次跳转到 4秒多
   //最后几页 连续向前翻页 1秒156毫秒

   //页号大范围跳转的时候需要的时间比较长,但是也小于1秒,同时SQL Server 占用的内存有所增加 120M。最后几页时达到320M

===================================================================
以下是多排序字段的分页情况,排序字段是 UnitPrice,ProductID  

   //分页算法2 无索引  首页 8秒187毫秒 。 
   //10 页以内 2秒812毫秒
   //速度太慢下面的就不测试了

   //分页2 非聚集索引 UnitPrice  首页 468毫秒
   //10 页以内 2秒671毫秒
   //速度太慢下面的就不测试了

   
   //分页算法2 非聚集索引 UnitPrice,ProductID  首页 500毫秒
   //10 页以内 2秒796毫秒
   //100页以内 4秒796毫秒
   //速度太慢下面的就不测试了

   
   //分页算法2 非聚集索引 UnitPrice,ProductID desc  首页 500毫秒
   //10 页以内 0-15毫秒
   //100页以内 15-46毫秒
   //1000页以内 31-62毫秒
   //10000页以内 100毫秒左右
   //50000页以内 400-500毫秒
   //100000页以内 900毫秒左右
   //最后几页 第一次跳转到 4秒421毫秒
   //最后几页 连续向前翻页 4秒375毫秒

   //页号大范围跳转的时候需要的时间比较长,但是也小于1秒,
   //这回SQL Server 占用的内存增加幅度不大 120M左右


posted on 2008-05-07 17:15 金色海洋(jyk) 阅读(2571) 评论(33)  编辑 收藏 所属分类: 自定义控件—分页控件

评论

#1楼 [楼主] 2008-05-07 19:30 金色海洋(jyk)      
自己路过一下。
  回复  引用  查看    

#2楼  2008-05-07 19:34 跟着心走      
学习了..
  回复  引用  查看    

#3楼  2008-05-07 19:56 潜水的青蛙      
飘过~
  回复  引用  查看    

#4楼  2008-05-07 20:13 birdshome      
专业路过,,,
  回复  引用  查看    

#5楼  2008-05-07 21:49 没剑      
无耻路过中。。。
  回复  引用  查看    

#6楼  2008-05-07 21:51 没名 [未注册用户]
无耻的路过,顺便说一句:博主不是普通的菜!
  回复  引用    

#7楼  2008-05-08 00:15 hutter001 [未注册用户]
感觉写得不错``
对我很有帮助``

第二种方法我从未用过``
  回复  引用    

我论坛的贴子.200W条记录.分页没有超过10毫秒
  回复  引用    

#9楼  2008-05-08 01:05 szaag [未注册用户]
数据大了建议还是分表
  回复  引用    

#10楼 [楼主] 2008-05-08 06:41 金色海洋(jyk)      
@没名 [未注册用户]
一看您就是高手,高手中的高高手,而且是一个老鸟,您一眼就看出来我是一菜鸟了,厉害呀。

@hutter001 [未注册用户]
谢谢,第一种方法您在那里看到的呀?

@轻舞flash [未注册用户]
能否透露一下您的论坛的网址,或者贴子的网址?我好去体验一下呀。

另外您是怎么分页的?能否说明一下,我好学习学习呀。

我是根据我的测试数据得出的时间。可能是我的cpu太差了,AMD 单核 3000+。我在测试的时候只看到了 0毫秒、15毫秒、30毫秒等,从来没有看到出现过10毫秒,看来我该升级电脑了。


@szaag [未注册用户]
分表是一种绕过困难的解决方法,有的时候可以分表,有的时候就不好分表。



  回复  引用  查看    

#11楼  2008-05-08 08:59 王孟军!      
挺细的
  回复  引用  查看    

#12楼  2008-05-08 09:36 8819 [未注册用户]
博主就是博客园典型的代表,所谓的高手
  回复  引用    

#13楼  2008-05-08 10:53 思考-总结      
加一句就能提高20%的效率,最少。
with(nolock)
  回复  引用  查看    

#14楼  2008-05-08 11:18 李战      
路过,真的学到了东西。谢谢!
  回复  引用  查看    

#15楼  2008-05-08 11:18 碎蜂      
这边在下雨,撑着伞路过....
  回复  引用  查看    

对于使用Top的情况,CPU占用比较高。
  回复  引用    

#17楼  2008-05-08 11:41 镜涛      
@8819
好像不止针对LZ
  回复  引用  查看    

#18楼 [楼主] 2008-05-08 11:52 金色海洋(jyk)      
我是菜鸟,不是高手。

with(nolock)我还不知道是甚么意思呢,一会查查帮助。

top ,如果能够利用索引的话,cpu占用是不多的。

SQL Server 在补丁包(比如sp4)里面对 top 和 in 都做了优化。

@王孟军! 、思考-总结 、李战
谢谢。
  回复  引用  查看    

#19楼  2008-05-08 12:09 lzppcc [未注册用户]
使用top分页,如果排序字段不是唯一的话,分页的内容是会随机变的.
  回复  引用    

#20楼  2008-05-08 12:20 PerfectDesign      
这种方式在N年前就已经流行了呀,海洋兄
而且你有没有测试过使用临时表+identity的方式来翻页?将临时库优化一下,这种方案要比你的正倒排序取分页速度要快不少。
这个数据我没有具体测试过,但是在sql2005技术内幕里面的作者有一个分页优化对比测试结果,你可以看看。
  回复  引用  查看    

#21楼  2008-05-08 12:22 PerfectDesign      
@金色海洋(jyk)
with(nolock)使得在选出数据时,不受任何锁的影响,即不会有任何等待发生。
  回复  引用  查看    

#22楼 [楼主] 2008-05-08 12:24 金色海洋(jyk)      
@lzppcc [未注册用户]

>>分页的内容是会随机变的
这个还没有发现,你可以自己试一试。

你说的是最后一个排序字段的值有重复的吧,比如说价格字段,8.00 元的产品有好几条,是不是这种情况呢?

这种情况的话,可以把 主键作为最后一个排序字段,这样就可以了,不会出现随机的情况。


  回复  引用  查看    

#23楼 [楼主] 2008-05-08 12:32 金色海洋(jyk)      
@PerfectDesign
我只见过一种颠颠倒倒的方法,和我的这个也不太一样。
其他的都是 max 之类的。

临时表+identity的方式,试过了。
吴起娃的分页控件就是用的这种方法吧,测试过了,

总体来说没有我的这种快,尤其是和第一种算法比(当然要在使用的前提下)。

===========

记录数:2523136条。
临时表的方式,由吴起娃的分页控件提供

insert into @indextable(nid) select ProductID from Products where categoryid=3 order by UnitPrice ,ProductID

select * from Products O,@indextable t where O.ProductID=t.nid
and t.id between @PageLowerBound+1 and @PageUpperBound order by t.id

显示第3页的数据,
第一次查询需要的时间:6秒,
再次查询:5秒。
内存占用:116M


显示第3000页的数据,
第一次查询需要的时间:28秒,
再次查询:5秒。
内存占用:116M

挺奇怪,为什么第3页和第3000页,再次查询需要的时间是一样的?

我的算法是越往前越快,后面的就慢了,但是也比临时表的快。


==========

记录数:2523136条。
我的第二种算法。(至少是我自己想出来的)

select * from Products where ProductID in
( select top 10 ProductID from
(select top 30000 ProductID , UnitPrice from
Products
where CategoryID = 3
order by
UnitPrice desc ,
ProductID
) as aa
order by
UnitPrice asc, --如果上面是倒序,那么这里就是正序,下同
ProductID desc
)
order by
UnitPrice desc, --如果上面是倒序,那么这里就是正序,所谓颠颠倒倒嘛。
ProductID

显示第3页的数据,
第一次查询需要的时间:10秒,
再次查询:小于1秒。
内存占用:62M


显示第3000页的数据,
第一次查询需要的时间:24秒,
再次查询:2到3秒。
内存占用:300M



  回复  引用  查看    

#24楼  2008-05-08 19:20 360 [未注册用户]
其实,并不需要对海量级的数据追求分页速度,研究这个真是浪费时间

如果你是实际用户,浏览1W级以上的数据时,你会一页一页又一页地从上翻到下吗?

真正最需要的是通过设置各种查询条件,一次又一次地缩小查询结果,最终只需要找到几百几千条数据,在这些结果里进行分页浏览,这样子才能快速地在海量数据中找到想要的结果

  回复  引用    

#25楼  2008-05-08 19:28 高_超      
路过中.又学到新东西了.呵呵.
  回复  引用  查看    

#26楼 [楼主] 2008-05-08 20:59 金色海洋(jyk)      
@360 [未注册用户]
其实都是相辅相成的,

“真正最需要的是通过设置各种查询条件,一次又一次地缩小查询结果,最终只需要找到几百几千条数据,在这些结果里进行分页浏览”

这个做好了,自然就会找到一个很高效的分页算法的。


  回复  引用  查看    

#27楼  2008-05-25 12:56 私家侦探      
sqlserver 真是害死不少程序员啊,干嘛2000的sqlserver不发布个补丁象2005那样用行号来分页呢,只有基于行号的分页才是最好的通用的
说句不好听的非行号的分页方法都很乱,不通用,在需求改变时彻底就乱了,惨了,比如想改其它排序字段,通用的才好,在需求变化时也能极少的修改代码,虽然分页速度未必最快,但也是很快的

写代码的时候要多想想,需求变了的时候要改多少代码?多不多?"对扩展开发对修改封闭"这是最好的设计也是理想中境界

行号的分页也就是传入一条自己任意写的sql语句,对这条语句是什么限制都没有啊,那才方便,象那种非要什么"排序字段有重复值没有重复值的""主键作为最后一个排序字段"等等都侵入性太强,强制要求别人这么做,可有时需求不是那样做的,这就冲突了,麻烦,"主键作为最后一个排序字段"会影响排序结果的知道不?
  回复  引用  查看    

#28楼  2008-05-25 13:00 私家侦探      
个人觉得max分页是最快的,如果主键是自增长的话,
颠颠倒倒的多次top分页次之,通用行比max好点
在来就是not in 分页,access好像就只能用这种,通用性最好
  回复  引用  查看    

#29楼  2008-05-25 18:01 wangwang0522      
楼主你觉得试图的性能怎么怎么样呀?
  回复  引用  查看    

#30楼 [楼主] 2008-05-25 19:34 金色海洋(jyk)      
@私家侦探
1、不要自己感觉,要测试,要思考。
2、max不是最快的,论速度,我的第一种算法是最快的,只是只适合sql Server。
3、第二种算法是可以在Access里面使用的。
4、,"主键作为最后一个排序字段"会影响排序结果的知道不?
我是知道的,但是我也知道,在最后一个字段有重复值得时候,是没有办法分页的,你说对于重复值,那条放在前一页,那条放在后一页?所以我把主建加进来作为一种判断。
5、确实不太好设置值,但是对于SQL Server 2000,我也没有别的办法呀。
6、对于扩展、升级。你有没有注意到呢?我是把算法放在了分页控件内部,就是说,当有变化的时候(比如数据库的变动,版本升级,发现了更好的算法等),只需要修改分页控件内部的代码就可以了。调用的部分是不用修改的。
这一点你没有发现吗?

  回复  引用  查看    

#31楼  2008-05-25 21:38 私家侦探      
我以前侧过三种方案,500百夺万的数据没骗你,用max估计和你的第一种方案速度查不多啊,搞不好还更快,好像都不用一秒,你的第一种方案倒是没有见过别人这么做,但是关键的地方似乎和max一样,都是利用自增长的主键,所以非常快
  回复  引用  查看    

#32楼  2008-06-04 22:07 银河使者      
现在sql server2005提供了分页的功能,用sql 2005就不需要这么麻烦。最喜欢mysql 的limit,非常简单。

好象新版的oracle也有类似分页的功能。为什么这些主流数据库不早点提供这种分矾的功能呢?很难吗?
  回复  引用  查看    

#33楼 [楼主] 2008-06-05 13:25 金色海洋(jyk)      
@私家侦探
利用的是索引,和是不是自增长的没有关系。
  回复  引用  查看    


标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-05-10 12:39 编辑过
 
另存  打印