rowid 與 rownum 分頁

轉自http://www.itpub.net/thread-1603830-1-1.html,作者Kevin__Zhang


一直以來,看到的說法大都是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
             (select  id,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
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         22  consistent gets
          0  physical reads
          0  redo size
       3703  bytes sent via SQL*Net to client
        590  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         99  rows processed

下面測試rowid分頁
11gR2 >select /*+ ordered use_nl(p s) */ *
      from (
            select rownum rn,rd
            from (select  rowid 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
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         19  consistent gets
          0  physical reads
          0  redo size
       5450  bytes sent via SQL*Net to client
        590  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         99  rows processed

我們看可以看到,當讀900-1000行時,rowid分頁(19邏輯讀)確實是優於rownum分頁(22邏輯讀)。因爲rowid方式減少了前900行的回表。

我們來看看10g的情況。
在10g建立同樣的TEST表和索引,然後:
測試10g的rownum分頁:
10gR2 >select *
       from (
             select rownum rn,t.*
             from
             (select  id,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
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         19  consistent gets
          0  physical reads
          0  redo size
       3842  bytes sent via SQL*Net to client
        558  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         99  rows processed

19個邏輯讀,和11g是很接近的,是個正常值,也符合我們的預期。

繼續測試10g的rowid分頁:
swp1 >select /*+ ordered use_nl(p s) */ *
      from (
            select rownum rn,rd
            from (select  rowid 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
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        109  consistent gets
          1  physical reads
          0  redo size
       5585  bytes sent via SQL*Net to client
        558  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         99  rows 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
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        875  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         10  rows 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
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         10  consistent gets
          0  physical reads
          0  redo size
        861  bytes sent via SQL*Net to client
        492  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         10  rows 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方式。



PS:之前發了疑問在管理版,貌似不是很多人關注,重新整理了實驗發在開發版,求討論和斧正。)





ROWID                      ID
------------------ ----------
AAAzo5AABAAAKnWAAA         30
AAAzo5AABAAAKnWAAB          8
AAAzo5AABAAAKnWAAC         14
AAAzo5AABAAAKnWAAD         34
AAAzo5AABAAAKnWAAE         45
AAAzo5AABAAAKnWAAF         39
AAAzo5AABAAAKnWAAG         47
AAAzo5AABAAAKnWAAH         51
AAAzo5AABAAAKnWAAI         11
AAAzo5AABAAAKnWAAJ         48

從rowid的名字就可以看出出來,一個rowid是這樣組成的:
OOOOOOFFFBBBBBBRRR,佔用10個字節(32bit+10bit rfile#+22bit+16bit)。其中,O是對象ID,F是文件ID,B是塊ID,R是行ID

所以我們可以看到以上的那些rowid,前面的對象號,文件號,塊號都是一樣的。只有行號有區別。
所以說是一個block的。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章