博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SQL分页排序的实现与分页数据重复问题——以Oracle rownum为例
阅读量:4975 次
发布时间:2019-06-12

本文共 2822 字,大约阅读时间需要 9 分钟。

对于关系数据库来说,直接写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页,等。显然,我们分页的步骤如下:

计算总记录数; 根据总记录数和每页记录数,计算总页数; 根据当前要显示的页码,计算起始和结束的记录号; 生成分页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都是一致的顺序了。

本篇文章来源于 Linux公社网站()  原文链接:

转载于:https://www.cnblogs.com/ozheric/p/3357528.html

你可能感兴趣的文章
chrome调试技巧和插件介绍
查看>>
线性表(顺序表的创建)
查看>>
linux下rm -r误删NTFS文件恢复方法
查看>>
SQL Server 第三堂课,学习数据库函数。跟C#语言异曲同工,同样是由输入参数,输出参数,函数体,返回值四要素组成,不同的是语法和写法。掌握知识的关键在与学好C#语言的函数...
查看>>
WPF编程—样式
查看>>
POJ 2817 WordStack(状态压缩DP)
查看>>
Java List&Map简单初始化方法
查看>>
canvas --> getImageData()
查看>>
python找递归目录中文件,并移动到一个单独文件夹中,同时记录原始文件路径信息...
查看>>
第四次作业--测试作业
查看>>
FPGA的嵌入式乘法器
查看>>
Spring当中的生命周期的方法的几点疑问
查看>>
hls视频播放-web视频播放
查看>>
HTML基础
查看>>
Vue 学习随笔六 - Directive添加以及form绑定
查看>>
VMware 连接不上XSHELL
查看>>
Java高级架构师(一)第38节:Nginx的负载均衡模块
查看>>
《Python黑帽子:黑客与渗透测试编程之道》 自动化攻击取证
查看>>
CE修改器使用教程 [基础篇]
查看>>
C++获取系统信息(IP地址、硬件信息等)
查看>>