最近遇到了这个问题,整理下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的字段要放在联合索引的最后。
没有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;
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;
也可以使用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;
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;
参考
rownum应用系列之分页查询
rownum应用系列之分页查询--续1
oracle高效分页查询总结 - 风吹过的绿洲 - 博客园