分页框架
错误的分页框架:
select *
from (select t.*, rownum rn from (需要分页的SQL) t)
where rn >= 1
and rn <= 10;
正确的分页框架:
select *
from (select *
from (select a.*, rownum rn from (需要分页的SQL) a)
where rownum <= 10)
where rn >= 1;
测试
SQL> create table test as select * from dba_objects;
Table created.
SQL> select *
2 from (select t.*, rownum rn
3 from (select * from test order by object_id) t)
4 where rn >= 1
5 and rn <= 10;
10 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3603170480
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 75798 | 15M| | 3702 (1)| 00:00:45 |
|* 1 | VIEW | | 75798 | 15M| | 3702 (1)| 00:00:45 |
| 2 | COUNT | | | | | | |
| 3 | VIEW | | 75798 | 14M| | 3702 (1)| 00:00:45 |
| 4 | SORT ORDER BY | | 75798 | 14M| 17M| 3702 (1)| 00:00:45 |
| 5 | TABLE ACCESS FULL| TEST | 75798 | 14M| | 290 (1)| 00:00:04 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("RN"<=10 AND "RN">=1)
Note
-----
- dynamic sampling used for this statement (level=2)
可以看到,该SQL在排序后取10行数据,SQL走全表扫。
创建索引:
SQL> create index idx_test on test(object_id,0);
Index created.
SQL强制走索引:
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------
SQL_ID dbcvrrsh6aw2y, child number 0
-------------------------------------
select /*+gather_plan_statistics*/ * from (select t.*, rownum rn
from (select /*+index(test idx_test)*/ *
from test order by object_id) t) where
rn >= 1 and rn <= 10
Plan hash value: 3119682446
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 1288 |
|* 1 | VIEW | | 1 | 75798 | 10 |00:00:00.01 | 1288 |
| 2 | COUNT | | 1 | | 72571 |00:00:00.03 | 1288 |
| 3 | VIEW | | 1 | 75798 | 72571 |00:00:00.02 | 1288 |
| 4 | TABLE ACCESS BY INDEX ROWID| test | 1 | 75798 | 72571 |00:00:00.02 | 1288 |
| 5 | INDEX FULL SCAN | IDX_TEST | 1 | 75798 | 72571 |00:00:00.01 | 183 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------
1 - filter(("RN"<=10 AND "RN">=1))
SQL强制走索引,索引全扫,扫描全部数据,逻辑读1288。
索引本身具有排序功能,应该是只扫描部分叶子块就获取我们想要的数据。
代入正确的分页框架:
select *
from (select *
from (select a.*, rownum rn
from (select /*+index(test idx_test)*/
*
from test
order by object_id) a)
where rownum <= 10)
where rn >= 1;
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------
SQL_ID 95puvbgwqw5mh, child number 0
-------------------------------------
select * from (select * from (select a.*, rownum rn
from (select /*+index(test idx_test)*/
* from test
order by object_id) a) where rownum <= 10) where rn >= 1
Plan hash value: 1201925926
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 5 |
|* 1 | VIEW | | 1 | 10 | 10 |00:00:00.01 | 5 |
|* 2 | COUNT STOPKEY | | 1 | | 10 |00:00:00.01 | 5 |
| 3 | VIEW | | 1 | 75798 | 10 |00:00:00.01 | 5 |
| 4 | COUNT | | 1 | | 10 |00:00:00.01 | 5 |
| 5 | VIEW | | 1 | 75798 | 10 |00:00:00.01 | 5 |
| 6 | TABLE ACCESS BY INDEX ROWID| test | 1 | 75798 | 10 |00:00:00.01 | 5 |
| 7 | INDEX FULL SCAN | IDX_test | 1 | 75798 | 10 |00:00:00.01 | 3 |
-------------------------------------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("RN">=1)
2 - filter(ROWNUM<=10)
SQL走索引全扫,但是只扫描了10行数据,逻辑读为5;利用count stopkey特性,获取到分页语句需要的数据,SQL立即停止运行
优化思路
分页语句的优化思路:
如果分页语句中有排序(order by),要利用索引已经排序特性,将order by的列包含在索引中,同时利用rownum的count stopkey特性来优化分页SQL。
如果分页中没有排序,可以直接利用rownum的COUNT STOPKEY特性来优化分页SQL。