kevin.zhang 发表于 2012-4-20 07:58:52

【案例5】你是否仍迷信rowid分页?

一直以来,看到的说法大都是rowid要强于rownum分页:
在索引设计的没问题的前提下,两种方式在前几页性能上接近,越向后翻rowid的效果越明显。

注意红字部分,红字部分一定成立吗?实验说明一切。

我们先来看看11g的情况,建立实验环境:
11gR2 >create table test(id number,status VARCHAR2(7),type VARCHAR2(19),created date);
Table created.
11gR2 >insert into test select OBJECT_ID,STATUS,OBJECT_TYPE,CREATED from dba_objects;
12926 rows created.
11gR2 >alter table test modify created not null;
Table altered.
11gR2 >create index test_ind1 on test(CREATED);
Index created.
11gR2 >ANALYZE TABLE TEST compute statistics;
Table analyzed.

测试11g的rownum分页:
11gR2 >select *
       from (
             select rownum rn,t.*
             from
             (selectid,status,type,created from test order by created) t
             where rownum<1000)
       where rn >900;
99 rows selected.
--------------------------------------------------------------------------------------------
| Id| Operation                      | Name      | Rows| Bytes | Cost (%CPU)| Time   |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |         |   999 | 50949 |   9   (0)| 00:00:01 |
|*1 |VIEW                        |         |   999 | 50949 |   9   (0)| 00:00:01 |
|*2 |   COUNT STOPKEY                |         |       |       |            |          |
|   3 |    VIEW                        |         |   999 | 37962 |   9   (0)| 00:00:01 |
|   4 |   TABLE ACCESS BY INDEX ROWID| TEST      | 12926 |   277K|   9   (0)| 00:00:01 |
|   5 |      INDEX FULL SCAN         | TEST_IND1 |   999 |       |   4   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
          0recursive calls
          0db block gets
         22consistent gets
          0physical reads
          0redo size
       3703bytes sent via SQL*Net to client
      590bytes received via SQL*Net from client
          8SQL*Net roundtrips to/from client
          0sorts (memory)
          0sorts (disk)
         99rows processed

下面测试rowid分页
11gR2 >select /*+ ordered use_nl(p s) */ *
      from (
            select rownum rn,rd
            from (selectrowid rd from test order by created)
            t where rownum<1000) p,
            test s
      where rn>900 and p.rd=s.rowid;2    3    4    5    6    7
99 rows selected.
-----------------------------------------------------------------------------------------
| Id| Operation                   | Name      | Rows| Bytes | Cost (%CPU)| Time   |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |   999 | 52947 |1003   (0)| 00:00:13 |
|   1 |NESTED LOOPS               |         |   999 | 52947 |1003   (0)| 00:00:13 |
|*2 |   VIEW                      |         |   999 | 24975 |   4   (0)| 00:00:01 |
|*3 |    COUNT STOPKEY            |         |       |       |            |          |
|   4 |   VIEW                  |         |   999 | 11988 |   4   (0)| 00:00:01 |
|   5 |      INDEX FULL SCAN      | TEST_IND1 | 12926 |   239K|   4   (0)| 00:00:01 |
|   6 |   TABLE ACCESS BY USER ROWID| TEST      |   1 |    28 |   1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
          0recursive calls
          0db block gets
         19consistent gets
          0physical reads
          0redo size
       5450bytes sent via SQL*Net to client
      590bytes received via SQL*Net from client
          8SQL*Net roundtrips to/from client
          0sorts (memory)
          0sorts (disk)
         99rows processed

我们看可以看到,当读900-1000行时,rowid分页(19逻辑读)确实是优于rownum分页(22逻辑读)。因为rowid方式减少了前900行的回表。

我们来看看10g的情况。
在10g建立同样的TEST表和索引,然后:
测试10g的rownum分页:
10gR2 >select *
       from (
             select rownum rn,t.*
             from
             (selectid,status,type,created from test order by created) t
             where rownum<1000)
       where rn >900;
99 rows selected.
--------------------------------------------------------------------------------------------
| Id| Operation                      | Name      | Rows| Bytes | Cost (%CPU)| Time   |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |         |   999 | 50949 |    17   (0)| 00:00:01 |
|*1 |VIEW                        |         |   999 | 50949 |    17   (0)| 00:00:01 |
|*2 |   COUNT STOPKEY                |         |       |       |            |          |
|   3 |    VIEW                        |         |   999 | 37962 |    17   (0)| 00:00:01 |
|   4 |   TABLE ACCESS BY INDEX ROWID| TEST      | 45620 |1113K|    17   (0)| 00:00:01 |
|   5 |      INDEX FULL SCAN         | TEST_IND1 |   999 |       |   3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
          0recursive calls
          0db block gets
       19consistent gets
          0physical reads
          0redo size
       3842bytes sent via SQL*Net to client
      558bytes received via SQL*Net from client
          8SQL*Net roundtrips to/from client
          0sorts (memory)
          0sorts (disk)
         99rows processed

19个逻辑读,和11g是很接近的,是个正常值,也符合我们的预期。

继续测试10g的rowid分页:
swp1 >select /*+ ordered use_nl(p s) */ *
      from (
            select rownum rn,rd
            from (selectrowid rd from test order by created)
            t where rownum<1000) p,
            test s
      where rn>900 and p.rd=s.rowid;
2    3    4    5    6    7
99 rows selected.
-----------------------------------------------------------------------------------------
| Id| Operation                   | Name      | Rows| Bytes | Cost (%CPU)| Time   |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |   999 | 55944 |1061   (1)| 00:00:15 |
|   1 |NESTED LOOPS               |         |   999 | 55944 |1061   (1)| 00:00:15 |
|*2 |   VIEW                      |         |   999 | 24975 |    62   (2)| 00:00:01 |
|*3 |    COUNT STOPKEY            |         |       |       |            |          |
|   4 |   VIEW                  |         | 45620 |   534K|    62   (2)| 00:00:01 |
|   5 |      INDEX FULL SCAN      | TEST_IND1 | 45620 |   846K|    62   (2)| 00:00:01 |
|   6 |   TABLE ACCESS BY USER ROWID| TEST      |   1 |    31 |   1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
          0recursive calls
          0db block gets
      109consistent gets
          1physical reads
          0redo size
       5585bytes sent via SQL*Net to client
      558bytes received via SQL*Net from client
          8SQL*Net roundtrips to/from client
          0sorts (memory)
          0sorts (disk)
         99rows processed

怎么逻辑读变成了109? 执行计划是和11g一样的。为什么会有这么大的差别?
rowid分页的cost一下变成了rownum方式的好几倍。

为什么?

下面的实验揭露了原因,也为我们揭露了11g一个不为人知的新特性:
11gR2 >select rowid,id from test where rowid in ('AAADSHAABAAAH3hAAA','AAADSHAABAAAH3hAAB','AAADSHAABAAAH3hAAC','AAADSHAABAAAH3hAAD','AAADSHAABAAAH3hAAE','AAADSHAABAAAH3hAAF','AAADSHAABAAAH3hAAG','AAADSHAABAAAH3hAAH','AAADSHAABAAAH3hAAI','AAADSHAABAAAH3hAAJ');
ROWID                      ID
------------------ ----------
AAADSHAABAAAH3hAAA         20
AAADSHAABAAAH3hAAB         46
AAADSHAABAAAH3hAAC         28
AAADSHAABAAAH3hAAD         15
AAADSHAABAAAH3hAAE         29
AAADSHAABAAAH3hAAF          3
AAADSHAABAAAH3hAAG         25
AAADSHAABAAAH3hAAH         41
AAADSHAABAAAH3hAAI         54
AAADSHAABAAAH3hAAJ         40
10 rows selected.
------------------------------------------------------------------------------------
| Id| Operation                   | Name | Rows| Bytes | Cost (%CPU)| Time   |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |      |   1 |    16 |   1   (0)| 00:00:01 |
|   1 |INLIST ITERATOR            |      |       |       |            |          |
|   2 |   TABLE ACCESS BY USER ROWID| TEST |   1 |    16 |   1   (0)| 00:00:01 |
------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
          0recursive calls
          0db block gets
          2consistent gets
          0physical reads
          0redo size
      875bytes sent via SQL*Net to client
      524bytes received via SQL*Net from client
          2SQL*Net roundtrips to/from client
          0sorts (memory)
          0sorts (disk)
         10rows processed

10gR2 >select rowid,id from test where rowid in ('AAAzo5AABAAAKnWAAA','AAAzo5AABAAAKnWAAB','AAAzo5AABAAAKnWAAC','AAAzo5AABAAAKnWAAD','AAAzo5AABAAAKnWAAE','AAAzo5AABAAAKnWAAF','AAAzo5AABAAAKnWAAG','AAAzo5AABAAAKnWAAH','AAAzo5AABAAAKnWAAI','AAAzo5AABAAAKnWAAJ');
ROWID                      ID
------------------ ----------
AAAzo5AABAAAKnWAAA         30
AAAzo5AABAAAKnWAAB          8
AAAzo5AABAAAKnWAAC         14
AAAzo5AABAAAKnWAAD         34
AAAzo5AABAAAKnWAAE         45
AAAzo5AABAAAKnWAAF         39
AAAzo5AABAAAKnWAAG         47
AAAzo5AABAAAKnWAAH         51
AAAzo5AABAAAKnWAAI         11
AAAzo5AABAAAKnWAAJ         48
10 rows selected.
------------------------------------------------------------------------------------
| Id| Operation                   | Name | Rows| Bytes | Cost (%CPU)| Time   |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |      |   1 |    16 |   1   (0)| 00:00:01 |
|   1 |INLIST ITERATOR            |      |       |       |            |          |
|   2 |   TABLE ACCESS BY USER ROWID| TEST |   1 |    16 |   1   (0)| 00:00:01 |
------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
          1recursive calls
          0db block gets
         10consistent gets
          0physical reads
          0redo size
      861bytes sent via SQL*Net to client
      492bytes received via SQL*Net from client
          2SQL*Net roundtrips to/from client
          0sorts (memory)
          0sorts (disk)
         10rows processed

我们看到,同样用rowid读取同一个block的10行,在11g中仅仅耗费了2个读,而在10g中耗费10个读。
更进一步的测试也确认了10g中,每一个rowid都会产生一个逻辑读,即使这些rowid来自一个block,也不能重用。
而11g引入了对rowid读的新特性, 那就是,如果rowid是来自一个block,那么是可以重用的。

总结:
在11g中,得益于这个11g不为人知的新特性,总体来说,rowid分页要优于rownum。
可是在10g中,相当一部分情况下rowid方式性能是远远不如rownum方式的。只有在后翻的页数非常大,并且页的大小很小,譬如10000-10010的情况下,rowid的分页才会优于rownum方式。


经纪人 发表于 2012-5-3 10:18:30

老师的实验讲的很细,致敬!:)
页: [1]
查看完整版本: 【案例5】你是否仍迷信rowid分页?