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
Execution Plan
----------------------------------------------------------
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
:01 |
--------------------------------------------------------------------------------
-----
Predicate Information (identified by operation id):
---------------------------------------------------
   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;
DJH0
-------------
CB15081800001
Execution Plan
----------------------------------------------------------
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 |
--------------------------------------------------------------------------------
---
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM=1)
Statistics
----------------------------------------------------------
      1  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>


###测试
测试表 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;
     STUID
----------
     1
Execution Plan
----------------------------------------------------------
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)
| 00:00:01 |
|*  2 |   COUNT STOPKEY     |               |       |       |
|       |
|   3 |    TABLE ACCESS FULL| tab1 |     1 |     3 |     2   (0)
| 00:00:01 |
--------------------------------------------------------------------------------
------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(ROWNUM=1)
Statistics
----------------------------------------------------------
     19  recursive calls
      0  db block gets
     13  consistent gets
      0  physical reads
      0  redo size
    523  bytes sent via SQL*Net to client
    520  bytes received via SQL*Net from client
      2  SQL*Net roundtrips to/from client
      4  sorts (memory)
      0  sorts (disk)
      1  rows processed
SYS@orcl1>

没有主键,rownum=1 取到的是最小值;加上排序,取到的也是1,执行计划看出来,是先取数再排序的。

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

此时执行sql
SYS@orcl1>select STUID from  tab1 where rownum=1;
     STUID
----------
     1

有主键,rownum=1 取到的仍是最小值;
SYS@orcl1>set autot on
SYS@orcl1>select STUID from  tab1 where rownum=1 order by 1 desc;
     STUID
----------
       100
Execution Plan
----------------------------------------------------------
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
:00:01 |
--------------------------------------------------------------------------------
--------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM=1)
Statistics
----------------------------------------------------------
      1  recursive calls
      0  db block gets
      5  consistent gets
      0  physical reads
      0  redo size
    523  bytes sent via SQL*Net to client
    520  bytes received via SQL*Net from client
      2  SQL*Net roundtrips to/from client
      1  sorts (memory)
      0  sorts (disk)
      1  rows processed
再加上排序,虽然rown=1一个条件是取到是最小值,但是先走的排序再取的rownum,这是我们预期的。


再来看看 rownum <50的情况
SYS@orcl1>select STUID from  tab1 where rownum<50 order by 1 desc;
     STUID
----------
       100
    99
    98
    97
    96
    95
    94
    93
    92
    91
    90
     STUID
----------
    89
    88
    87
    86
    85
    84
    83
    82
    81
    80
    79
     STUID
----------
    78
    77
    76
    75
    74
    73
    72
    71
    70
    69
    68
     STUID
----------
    67
    66
    65
    64
    63
    62
    61
    60
    59
    58
    57
     STUID
----------
    56
    55
    54
    53
    52
49 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 1084965663
--------------------------------------------------------------------------------
--------
| Id  | Operation            | Name     | Rows  | Bytes | Cost (%CPU)| Ti
me     |
--------------------------------------------------------------------------------
--------
|   0 | SELECT STATEMENT        |           |    49 |   147 |     1     (0)| 00
:00:01 |
|*  1 |  COUNT STOPKEY            |           |       |       |        |
       |
|   2 |   INDEX FULL SCAN DESCENDING| PK_STUID |    49 |   147 |     1     (0)| 00
:00:01 |
--------------------------------------------------------------------------------
--------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM<50)
Statistics
----------------------------------------------------------
      1  recursive calls
      0  db block gets
      5  consistent gets
      0  physical reads
      0  redo size
       1379  bytes sent via SQL*Net to client
    553  bytes received via SQL*Net from client
      5  SQL*Net roundtrips to/from client
      0  sorts (memory)
      0  sorts (disk)
     49  rows processed
SYS@orcl1>
##对于客户情况,先对该情况做一个sql调整,使用嵌套查询
SYS@orcl1>select * from (select STUID from prvflat_stuinfo_part order by 1 desc ) where rownum=1;
     STUID
----------
       100
Execution Plan
----------------------------------------------------------
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
0:00:01 |
|   3 |    INDEX FULL SCAN DESCENDING| PK_STUID |   100 |   300 |     1   (0)| 0
0:00:01 |
--------------------------------------------------------------------------------
---------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM=1)
Statistics
----------------------------------------------------------
      1  recursive calls
      0  db block gets
      1  consistent gets
      0  physical reads
      0  redo size
    523  bytes sent via SQL*Net to client
    520  bytes received via SQL*Net from client
      2  SQL*Net roundtrips to/from client
      0  sorts (memory)
      0  sorts (disk)
      1  rows processed
SYS@orcl1>


#这个是时候,oracle逻辑似乎非常明显,只要order的字段是主键,就是先排序再取rownum,真的是这样吗?测试看来是正确的,但是客户遇到的又是怎样情况?难道是个别表的bug?

上一篇:Oracle 物化视图和物化视图日志


下一篇:MS-SQL异机备份恢复并最小化停机时间