对于关系数据库来说,直接写SQL拉数据在列表中显示是很常用的做法。但如此便带来一个问题:当数据量大到一定程度时,系统内存迟早会耗光。另外,网络传输也是问题。如果有1000万条数据,用户想看最后一条,这时即便有足够的内存,在网络上传输这么多数据也得一两小时吧,恐怕没几个用户有这么耐心等。因此分页是必须的。

现在网上的论坛、博客什么的,基本上都会有分页功能,有些是SQL分页的,有些可能是NOSQL用其它方法分页,都有很成熟的东西了。本文根据我自己的经验,以ORACLE为例,讲下简单的SQL分页和排序问题,对刚接触SQL准备要做分页的人有些帮助吧,大牛们就不必看了。

假设ORALCE数据库中有一个TAB001表,主键为ID,有1000万条记录,索引什么的都有了。我们有一个需求,是在界面上列出指定条件的记录,原始SQL如下:

select ID,NAME,ATYPE,CREATEDATE,CREATOR,ASTATUS from TAB001 where ATYPE='SOME_TYPE'

如果要排序,比如要按CREATOR倒排序,我们会在SQL后面再加一句:order by CREATEOR desc

现在,我们发现这个SQL下来有500万条记录,显然,如果不分页,系统很容易就会翘掉。于是我们准备分页。

分页前,我们可能要在界面上摆上几个按钮和状态显示:上一页、下一页、第一页、最后页、每页X条、共M页、当前第N页、跳到第N页,等。显然,我们分页的步骤如下:

  1. 计算总记录数;
  2. 根据总记录数和每页记录数,计算总页数;
  3. 根据当前要显示的页码,计算起始和结束的记录号;
  4. 生成分页SQL,执行之,返回本页数据,显示之。

首先,计算总记录数。这个简单,嵌套一个select count(*)就行了:

select count(*)
  from (
             select ID,NAME,ATYPE,CREATEDATE,CREATOR,ASTATUS from TAB001 where ATYPE='SOME_TYPE'
          ) xx
 

然后,总页数=ceil(总记录数/每页记录数),不足一页也当一页处理。

接着,假设现在是第N页,则本页的开始、结束记录号为:

开始记录号=N*每页记录数

结束记录号=min((N+1)*每页记录数-1,总记录数)

最后,生成分页SQL。由于分页需要有记录号,因此先要嵌套一个子查询生成ROWNUM:

select rownum as recordno
  from (
             select ID,NAME,ATYPE,CREATEDATE,CREATOR,ASTATUS from TAB001 where ATYPE='SOME_TYPE'
          ) xx
 

这样,我们就有了记录号,可以再对记录号进行过滤,只选出本页开始记录号之后、结束记录号之前的记录:

select xxx.*
  from (
            select rownum as recordno
              from (
                         select ID,NAME,ATYPE,CREATEDATE,CREATOR,ASTATUS from TAB001 where ATYPE='SOME_TYPE'
                      ) xx
           ) xxx
where recordno >= :开始记录号
    and recordno <= :结束记录号
 

至此似乎分页SQL已经完成了,表面上看这个SQL挺正确,运行起来似乎也没问题。但经过我们实践检验,其实这个SQL是不安全的,在某些情况下会出错,原因在于它没有排序。在分页情况下,第一页和第二页的数据是来自两次相对独立的SQL,如果没有排序,则SQL第一次和第二次执行时返回的结果是不一致的。

不一致是什么意思?假设有一个无排序的SQL,我们把SQL执行两次:

  • 第一次执行后会返回有1、2、3、4、5共5条记录
  • 第二次执行后还是会返回有1、2、3、4、5共5条记录

大部分情况下,这两次返回结果的顺序是完全一样的。但不幸的是,也许数据库有问题了,也许有人改了数据,反正有时候它会不一样,比如第二次执行时第2条和第4条对调了,返回的是1、4、3、2、5共5条记录,如下:

  • 第一次:1、2、3、4、5
  • 第二次:1、4、3、2、5

假设我们对这个SQL进行分页,每页3条记录,共两页,正常情况下结果是这样的:

  • 拉第一页时,执行第一次SQL,按1、2、3、4、5排序,返回1、2、3三条记录
  • 拉第二页时,执行第一次SQL,按1、2、3、4、5排序,返回4、5两条记录

但如果发生排序混乱的问题,结果会这样:

  • 拉第一页时,执行第一次SQL,按1、2、3、4、5排序,返回1、2、3三条记录
  • 拉第二页时,执行第二次SQL,按1、4、3、2、5排序,返回2、5两条记录

结果我们会发现,分页结果很不正常,2这条记录出现了两次,4则消失了。正常来说,我们不会注意到有数据丢失,但我们会注意到分页的数据有重复。

怎么办呢?那我们就加一个排序吧,排序子句要加在最里层的SQL里,这样分页出来的结果才会是排序后的结果。比如按名称、类别或作者排序的order by子句:

select xxx.*
  from (
            select rownum as recordno
              from (
                         select ID,NAME,ATYPE,CREATEDATE,CREATOR,ASTATUS from TAB001 where ATYPE='SOME_TYPE'
                          order by NAME,ATYPE,CREATOR
                      ) xx
           ) xxx
where recordno >= :开始记录号
    and recordno <= :结束记录号
 

这样是不是可以了呢?答案还是不行,因为这些字段的值不是唯一的。可考虑一个极端情况,就是这个表里500万条记录的名称、类别和作者都完全一样,会有什么结果呢?结果仍然是无序。

最终解决这个问题的办法,就是一定要用ID主键排序。不管前面有多少个order by字段,最后面一定要加上ID主键:

 
select xxx.*
  from (
            select rownum as recordno
              from (
                         select ID,NAME,ATYPE,CREATEDATE,CREATOR,ASTATUS from TAB001 where ATYPE='SOME_TYPE'
                          order by NAME,ATYPE,CREATOR,ID
                      ) xx
           ) xxx
where recordno >= :开始记录号
    and recordno <= :结束记录号
 

由于主键ID是唯一的,所以只要ID不变,按ID排序就能保证每次执行分页SQL都是一致的顺序了。

文章来源:http://blog.csdn.net/fengbonianshao/article/details/22856163

SQL分页数据重复问题的更多相关文章

  1. Mybatis oracle多表联合查询分页数据重复的问题

    Mybatis oracle多表联合查询分页数据重复的问题 多表联合查询分页获取数据时出现一个诡异的现象:数据总条数正确,但有些记录多了,有些记录却又少了甚至没了.针对这个问题找了好久,最后发现是由于 ...

  2. MySQL中orderby和limit分页数据重复的问题

    背景 读取规则是按照某表中sequence字段排序的,而这个字段是让人手工填写的.那么,可想而知,数据一多,难免会出现填写的值相同的情况. 综上所述,可能就会导致以下两条sql出现数据重叠的情况: s ...

  3. Oracle分页查询排序数据重复问题

    参考资料: http://docs.oracle.com/database/122/SQLRF/ROWNUM-Pseudocolumn.htm#SQLRF00255 http://blog.csdn. ...

  4. 为什么MYSQL分页时使用limit+ order by会出现数据重复问题

    问题描述: MYSQL采用limit进行翻页查询时,搭配order by ,在翻到第二页的时候可能会出现第一页的数据,  示例sql如下: select  a,b from c where d = ' ...

  5. iOS不得姐项目--推荐关注模块(一个控制器控制两个tableView),数据重复请求的问题,分页数据的加载,上拉下拉刷新(MJRefresh)

    一.推荐关注模块(一个控制器控制两个tableView) -- 数据的显示 刚开始加载数据值得注意的有以下几点 导航控制器会自动调整scrollView的contentInset,最好是取消系统的设置 ...

  6. 删除sql server中重复的数据

    原文:删除sql server中重复的数据 with list_numbers as( select Name, AuthorOrTime, Url, Price, EstimatePrice, Si ...

  7. 最通用的ibatis.Net使用sql server存储过程返回分页数据的详细例子

    ibatis.Net是一个比较简单和灵活的ORM框架,今天我分享一个我的项目中使用sql server通用存储过程来分页的一个例子,用ibatis.Net框架统一返回分页数据为IList<Has ...

  8. 解决 MySQL 分页数据错乱重复

    前言 一天,小明兴匆匆的在通讯工具上说:这边线上出现了个奇怪的问题,麻烦 DBA 大大鉴定下,执行语句 select xx from table_name wheere xxx order by 字段 ...

  9. sql server中的分页数据查询

    1.引言 今天在工作中遇到一个需要进行sql server分页数据查询的问题,但是分页数据查询的sql却忘记了,最终通过查询资料解决了该问题.现在把解决方法记下,以备查阅. 在这里需要感谢博客园的Ql ...

随机推荐

  1. C语言中以十六进制输出字符型变量会出现'ffffff"的问题

    最近在做一个C的嵌入式项目,发现在C语言中用printf()函数打印字符型变量时,如果想采用"%x"的格式将字符型变量值以十六进制形式打印出来,会出现一个小问题,如下: char  ...

  2. GDI+ 支持的图片文件格式

    您可以使用许多标准格式将位图储存在磁盘文件中.GDI+ 支持以下各种图片文件格式. o 位图 (BMP) 位图是 Windows 用来储存设备无关和与应用程序无关的图片的标准格式.文件头决定了指定的位 ...

  3. Delphi中BCD和Currency类型

    用了这些年的Delphi,竟然对Currency及TBCDField一知半解,下文给了很好的讲解,值得一读. 一.       BCD类型 BCD即Binary-Coded Decimal?,在Del ...

  4. (二)Jmeter各部件的作用

    JMeter主要组件介绍 1.测试计划(Test Plan)是使用 JMeter 进行测试的起点,它是其它 JMeter 测试元件的容器. 2.线程组(Thread Group)代表一定数量的并发用户 ...

  5. Three.js入门篇(一)创建一个场景

    上一面讲述了向场景中添加物体对象.这一篇准备把每个功能点细细的讲述一遍,一方面是为了加深自己的理解.另一方面希望能够 帮助到有需要的人. 一.在学习WEBGL的时候,你应该先了解要创建一个WebGL程 ...

  6. linux 搭建epel本地库,并定时同步

    1.安装rsyncyum -y install rsync.x86_64 2.同步epel至本地#http://mirrors.ustc.edu.cn/status/ 获取镜像库rsync路径mkdi ...

  7. python调用Sikuliapi

    Sikuli是由MIT(麻省理工学院) 研究团队发布的一种图形化编程技术(编程小白的福音),使用Sikuli你只需要会写HelloWorld这种最基本的编程技能即可,用Sikuli不需要去写出一行行复 ...

  8. office2013 激活方法

    1.秘钥码激活 可以淘宝上买一个. 2.KMS激活软件激活 帖子地址 http://tieba.baidu.com/p/3855281630 Office 2013 Professional Plus ...

  9. Mysql 的InnoDB事务方面的 多版本并发控制如何实现 MVCC

    Mysql的MVCC不能解决幻读的问题,但是Mysql还有间隙锁功能,Mysql的间隙锁工作在Repeatable Read隔离级别下面,可以防止幻读, 参考:Mysql 间隙锁原理,以及Repeat ...

  10. 多线程-Thread的run()与start()的区别

    总结: 1) start: 用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码.通过调用Thread类的start()方法来启动一个线程,这 ...