Oracle 分页查询的高效写法

最近遇到了这个问题,整理下Oracle 分页查询的高效写法

一、 低效与高效写法对比

低效的写法

select column_lists from (select rownum as rn,A.* from (select column_lists from table_name where col_1=:b0 order by col_2) A ) where rn<=:b2 and rn>:b1;

高效的写法

select column_lists from (select rownum as rn,A.* from (select column_lists from table_name where col_1=:b0 order by col_2) A where rownum<=:b2 ) where rn>:b1;

低效写法需要将内层的结果集全部排序,再从中取出需要的部分;而高效写法只需要获取排序后<=:b2部分的结果就可以了。一般分页查询访问前面部分页面的几率较大,内层查询的结果集越大,性能差距越明显。如果是访问分页的最后部分的页面,基本上就没什么差别了。

建议创建col_1和col_2字段上的联合索引,避免排序,提高效率。联合索引的字段顺序不能错,order by的字段要放在联合索引的最后。

二、 Oracle通用分页格式

1. 无order by

没有order by语句的分页,比有order by少一层

SELECT *
FROM (SELECT ROWNUM AS rowno, t.*
          FROM DONORINFO t
          WHERE t.BIRTHDAY BETWEEN TO_DATE (19800101, yyyymmdd)
          AND TO_DATE (20060731, yyyymmdd)
          AND ROWNUM <= page*size) table_alias
WHERE table_alias.rowno > (page-1)*size;

2. 有order by

SELECT *
FROM (SELECT ROWNUM AS rowno,r.*
           FROM(SELECT * FROM DONORINFO t
                    WHERE t.BIRTHDAY BETWEEN TO_DATE (19800101, yyyymmdd)
                    AND TO_DATE (20060731, yyyymmdd)
                    ORDER BY t.BIRTHDAY desc
                   ) r
           where ROWNUM <= page*size 
          ) table_alias
WHERE table_alias.rowno > (page-1)*size;

3. 分析函数

也可以使用row_number() over函数,但是并没有什么性能优势。

select * 
from(select d.*,row_number() over(order by d.BIRTHDAY) as rownumber 
       from DONORINFO d
       WHERE d.BIRTHDAY BETWEEN TO_DATE (19800101, yyyymmdd)
        AND TO_DATE (20060731, yyyymmdd)
 ) p 
where p.rownumber BETWEEN size*(page-1)+1 AND page*size;

如果是order by desc,正常情况优化器会自动使用index descending扫描方式,不需要建索引的时候加desc 。也有sql复杂的时候优化器没有使用index descending扫描方式,可以用index_desc来纠正。

select * from (select /*+ index_desc(d IDX_BIRTHDAY) */ d.*,row_number() over(order by d.BIRTHDAY desc) as rownumber from DONORINFO d WHERE d.BIRTHDAY BETWEEN TO_DATE (19800101, yyyymmdd) AND TO_DATE (20060731, yyyymmdd) ) p where p.rownumber BETWEEN size*(page-1)+1 AND page*size;

4. 12c 的offset写法

oracle 12c 中使用了简洁的offset 语法,本质是使用分析函数row_number()在内部做了改写,效率也很高。不过当前的主流写法还是上面使用rownum伪列的方法。

select /*+ index_desc(d IDX_BIRTHDAY) */ d.* from DONORINFO d WHERE d.BIRTHDAY BETWEEN TO_DATE (19800101, yyyymmdd) AND TO_DATE (20060731, yyyymmdd) ORDER BY t.BIRTHDAY desc offset 10 rows fetch next 10 rows only;

三、 分页使用误区

分页不适合用来做大结果集数据分片, 问题:

    返回记录数多,使用索引效率低,需要多次全表扫描 分页值越大,需要获取的记录数越多,效率越低 不能并发执行,表数据实时变化,会出现重复取数或漏取的问题

下面是一个典型的例子,都到百万行了还分页,效率很低

select column_lists from (select rownum as rn,A.* from (select column_lists from table_name where col_1=:b0 order by col_2) A where rownum<=1500000 ) where rn>1200000;

参考

经验分享 程序员 微信小程序 职场和发展