天天看點

Oracle 的sql陷阱(1)rownum和order by一起使用

#Oracle 的sql陷阱(1)rownum和order by一起使用

rownum和order by一起使用可能會遇到取數不準确的問題,客戶遇到了,我也測試了下,臨時解決辦法是使用嵌套查詢,先排序出來結果再rownum,這種效率不高,當然最高效的是oracle優化器自己知道如何去取你要的資料,但是有時卻不是100%準确,因為他不是一個100%的公式邏輯。

##客戶遇到的問題

某天,客戶突然發現取到的資料不對了,生成資料不對了,客戶的情況如下:

以前一直正常執行,且有類似業務結構,僅表名不同的表也還取數正常。

###遇到問題的表T_WCPB現在的執行計劃

SQL> SELECT DJH0  FROM ERP_CC.T_WCPB WHERE ROWNUM = 1 ORDER BY DJH0 desc;

DJH0

CB15081800001

Execution Plan

----------------------------------------------------------

Plan hash value: 678339681

------------------------------------------------------------------------------------

| Id  | Operation           | Name       | Rows  | Bytes | Cost (%CPU)| Time       |

|   0 | SELECT STATEMENT       |       |     1 |    14 |     3  (34)| 00:00:01 |

|   1 |  SORT ORDER BY           |       |     1 |    14 |     3  (34)| 00:00:01 |

|*  2 |   COUNT STOPKEY        |       |       |       |        |       |

|   3 |    INDEX FAST FULL SCAN| PK_T_WCPB |     1 |    14 |     2   (0)| 00:00:01 |

Predicate Information (identified by operation id):

---------------------------------------------------

   2 - filter(ROWNUM=1)

從執行計劃中我們看到oracle通過主鍵索引快速掃描取到所需資料後“COUNT STOPKEY”,再進行的排序“SORT ORDER BY”,rownum排在前面,肯定不是我們想要的。但是話說回來,你想要的是排序後的rownum,如果你想要的是rownum<n 的結果再排序呢?oracle 到底該如何決定先取數還是先排序?

###匪夷所思

ROWNUM = 1 兩個表取到的一個是該表的最大值,一個是最小值

SQL> SELECT DJH1  FROM ERP_CC.T_WXSCKD WHERE ROWNUM = 1;

DJH1

-------------

CK17081600121

Plan hash value: 577535013

--------------------------------------------------------------------------------

-----

| Id  | Operation          | Name        | Rows  | Bytes | Cost (%CPU)| Time

    |

|   0 | SELECT STATEMENT      |         |      1 |     14 |      2   (0)| 00:00

:01 |

|*  1 |  COUNT STOPKEY          |         |        |        |         |

|   2 |   INDEX FAST FULL SCAN| PK_T_WXSCKD |      1 |     14 |      2   (0)| 00:00

   1 - filter(ROWNUM=1)

Statistics

      0  recursive calls

      0  db block gets

      7  consistent gets

      0  physical reads

      0  redo size

    533  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)

      1  rows processed

SQL> SELECT DJH0  FROM ERP_CC.T_WCPB WHERE ROWNUM = 1;

Plan hash value: 44983662

---

| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time

  |

|   0 | SELECT STATEMENT      |       |    1 |    14 |    2   (0)| 00:00:0

1 |

|*  1 |  COUNT STOPKEY          |       |      |      |           |

|   2 |   INDEX FAST FULL SCAN| PK_T_WCPB |    1 |    14 |    2   (0)| 00:00:0

      1  recursive calls

SQL>

###測試

測試表 tab1 ,開始沒有設定主鍵,100記錄

SYS@orcl1>select STUID from  tab1 where rownum=1;

     STUID

----------

     1

SYS@orcl1>select STUID from  tab1 where rownum=1 order by 1 desc;

Plan hash value: 1612508337

------------

| Id  | Operation        | Name           | Rows  | Bytes | Cost (%CPU)

| Time       |

|   0 | SELECT STATEMENT    |               |     1 |     3 |     3  (34)

| 00:00:01 |

|   1 |  SORT ORDER BY        |               |     1 |     3 |     3  (34)

|*  2 |   COUNT STOPKEY     |               |       |       |

|       |

|   3 |    TABLE ACCESS FULL| tab1 |     1 |     3 |     2   (0)

     19  recursive calls

     13  consistent gets

    523  bytes sent via SQL*Net to client

    520  bytes received via SQL*Net from client

      4  sorts (memory)

SYS@orcl1>

沒有主鍵,rownum=1 取到的是最小值;加上排序,取到的也是1,執行計劃看出來,是先取數再排序的。

添加主鍵alter table tab1 add constraint pk_STUID  primary key(STUID);

此時執行sql

有主鍵,rownum=1 取到的仍是最小值;

SYS@orcl1>set autot on

       100

Plan hash value: 1084965663

--------

| Id  | Operation            | Name     | Rows  | Bytes | Cost (%CPU)| Ti

me     |

|   0 | SELECT STATEMENT        |           |     1 |     3 |     1     (0)| 00

:00:01 |

|*  1 |  COUNT STOPKEY            |           |       |       |        |

       |

|   2 |   INDEX FULL SCAN DESCENDING| PK_STUID |     1 |     3 |     1     (0)| 00

      5  consistent gets

      1  sorts (memory)

再加上排序,雖然rown=1一個條件是取到是最小值,但是先走的排序再取的rownum,這是我們預期的。

再來看看 rownum <50的情況

SYS@orcl1>select STUID from  tab1 where rownum<50 order by 1 desc;

    99

    98

    97

    96

    95

    94

    93

    92

    91

    90

    89

    88

    87

    86

    85

    84

    83

    82

    81

    80

    79

    78

    77

    76

    75

    74

    73

    72

    71

    70

    69

    68

    67

    66

    65

    64

    63

    62

    61

    60

    59

    58

    57

    56

    55

    54

    53

    52

49 rows selected.

|   0 | SELECT STATEMENT        |           |    49 |   147 |     1     (0)| 00

|   2 |   INDEX FULL SCAN DESCENDING| PK_STUID |    49 |   147 |     1     (0)| 00

   1 - filter(ROWNUM<50)

       1379  bytes sent via SQL*Net to client

    553  bytes received via SQL*Net from client

      5  SQL*Net roundtrips to/from client

     49  rows processed

##對于客戶情況,先對該情況做一個sql調整,使用嵌套查詢

SYS@orcl1>select * from (select STUID from prvflat_stuinfo_part order by 1 desc ) where rownum=1;

Plan hash value: 825904777

---------

| Id  | Operation             | Name    | Rows    | Bytes | Cost (%CPU)| T

ime    |

|   0 | SELECT STATEMENT         |        |     1 |    13 |     1   (0)| 0

0:00:01 |

|*  1 |  COUNT STOPKEY             |        |    |    |         |

|   2 |   VIEW                 |        |     1 |    13 |     1   (0)| 0

|   3 |    INDEX FULL SCAN DESCENDING| PK_STUID |   100 |   300 |     1   (0)| 0

      1  consistent gets

#這個是時候,oracle邏輯似乎非常明顯,隻要order的字段是主鍵,就是先排序再取rownum,真的是這樣嗎?測試看來是正确的,但是客戶遇到的又是怎樣情況?難道是個别表的bug?