Oracle分析函式多層使用時的一個bug及其解決方案

regonly1發表於2011-07-08
前幾天因為客戶資料統計錯誤,引起強烈反應,加班加點終於把問題解決。但是最終結果卻出人意料,經排查,確定並非我們程式碼的問題,而是的一個bug。bug號及描述:
6840911 Wrongs results from WINDOW NOSORT in execution plan
參考連結:

簡單的說就是在某些情況下,執行計劃會產生window nosort的操作,而使用該操作的分析函式可能會引起錯誤的結果。
該問題在10.2.0.5版本及以上已經修復。
這兩天有空,正好把這個問題總結一下,也提醒看到這篇總結的人遇到此類錯誤時有所參考,或在編碼中有意識的避開該bug。

【說明】:以下表中的C欄位不是必要條件,沒有該欄位還是會出現此問題。

測試資料:
create table abcccc(a number, b char(1), c number);
insert into abcccc (A, B, C) values (1, 'x', 1);
insert into abcccc (A, B, C) values (1, 'x', 1);
insert into abcccc (A, B, C) values (1, 'x', 1);
insert into abcccc (A, B, C) values (1, 'y', 1);
insert into abcccc (A, B, C) values (1, 'y', 1);
insert into abcccc (A, B, C) values (1, 'z', 1);
insert into abcccc (A, B, C) values (1, 'z', 1);
insert into abcccc (A, B, C) values (1, 'z', 1);
insert into abcccc (A, B, C) values (1, 'z', 1);
commit;

查詢結果:
select * from abcccc;

A    B    C
1    x    1
1    x    1
1    x    1
1    y    1
1    y    1
1    z    1
1    z    1
1    z    1
1    z    1

以下兩個查詢的不同在於外層的兩個decode(以下紅色部分),其他完全相同:
查詢1:
select dense_rank() over (order by a,decode(b||'', 'X', 'Y', b), c) groupid, a, b, c, cntall
  from (select a, b, c, count(*)over(partition by a, decode(b, 'X', 'Y', b), c) cntall
          from abcccc) t
它的外層decode是這樣的:
decode(b||'', 'X', 'Y', b)

查詢2:
select dense_rank() over (order by a,decode(b, 'X', 'Y', b), c) groupid, a, b, c, cntall
  from (select a, b, c, count(*)over(partition by a, decode(b, 'X', 'Y', b), c) cntall
          from abcccc) t
它的外層decode是這樣的:
decode(b, 'X', 'Y', b)

從含義上來看,兩者沒有本質的區別,但是查詢結果卻是有很大的不同(注意groupid欄位):
第一個查詢的結果:
SQL> select dense_rank() over (order by a, decode(b||'', 'X', 'Y', b), c) groupid, a, b, c, cntall
  2    from (select a, b, c, count(*)over(partition by a, decode(b, 'X', 'Y', b), c) cntall
  3            from abcccc) t
  4  /
 
   GROUPID          A B          C     CNTALL
---------- ---------- - ---------- ----------
        1          1 x          1          3
        1          1 x          1          3
        1          1 x          1          3
        2          1 y          1          2
                 1 y          1          2
        3          1 z          1          4
        3          1 z          1          4
        3          1 z          1          4
        3          1 z          1          4
 
9 rows selected

第二個查詢的結果:
SQL> select dense_rank() over (order by a, decode(b, 'X', 'Y', b), c) groupid, a, b, c, cntall
  2    from (select a, b, c, count(*)over(partition by a, decode(b, 'X', 'Y', b), c) cntall
  3            from abcccc) t
  4  /
 
   GROUPID          A B          C     CNTALL
---------- ---------- - ---------- ----------
        1          1 x          1          3
        1          1 x          1          3
        1          1 x          1          3
        1          1 y          1          2
        1          1 y          1          2
        1          1 z          1          4
        1          1 z          1          4
                 1 z          1          4
        1          1 z          1          4
 
9 rows selected

可以看到,兩個的groupid差異非常大,根據語句含義,實際上要的是查詢一的結果,但是查詢二卻出現瞭如此怪異的結果。
而實際上,正常的編碼應該是查詢二的這種方式。並非查詢一的經過加工的。
嘗試在內層查詢使用其他分析函式 ,看是否會出現這個問題。
先用sum試一下:
SQL> select dense_rank() over (order by a, decode(b, 'X', 'Y', b), c) groupid, a, b, c, cntall
  2    from (select a, b, c, sum(a)over(partition by a, decode(b, 'X', 'Y', b), c) cntall
  3            from abcccc) t
  4  /
 
   GROUPID          A B          C     CNTALL
---------- ---------- - ---------- ----------
         1          1 x          1          3
         1          1 x          1          3
         1          1 x          1          3
         1          1 y          1          2
         1          1 y          1          2
         1          1 z          1          4
         1          1 z          1          4
         1          1 z          1          4
         1          1 z          1          4
 
9 rows selected
這個查詢也有問題。

再用lead:
SQL> select dense_rank() over (order by a, decode(b, 'X', 'Y', b), c) groupid, a, b, c, cntall
  2    from (select a, b, c, lead(a)over(partition by a, decode(b, 'X', 'Y', b), c order by rownum) cntall
  3            from abcccc) t
  4  /
 
   GROUPID          A B          C     CNTALL
---------- ---------- - ---------- ----------
         1          1 x          1          1
         1          1 x          1          1
         1          1 x          1
         1          1 y          1          1
         1          1 y          1
         1          1 z          1          1
         1          1 z          1          1
         1          1 z          1          1
         1          1 z          1
 
9 rows selected
也有問題;
從這裡可以知道,任何分析函式都有可能會遇到這個問題。

不用partition by子句,改用order by子句:
SQL> select dense_rank() over (order by a, decode(b, 'X', 'Y', b), c) groupid, a, b, c, cntall
  2    from (select a, b, c, count(*)over(order by a, decode(b, 'X', 'Y', b), c) cntall
  3            from abcccc) t
  4  /
 
   GROUPID          A B          C     CNTALL
---------- ---------- - ---------- ----------
         1          1 x          1          3
         1          1 x          1          3
         1          1 x          1          3
         1          1 y          1          5
         1          1 y          1          5
         1          1 z          1          9
         1          1 z          1          9
         1          1 z          1          9
         1          1 z          1          9
 
9 rows selected
還是有問題。
到這裡,又可以知道,並非只有partition by才會出現問題,order by也有可能。

那會不會是decode的緣故引起的呢?再次驗證:
SQL> select dense_rank() over (order by a, b, c) groupid, a, b, c, cntall
  2    from (select a, b, c, count(*)over(order by a, b, c) cntall
  3            from abcccc) t
  4  /
 
   GROUPID          A B          C     CNTALL
---------- ---------- - ---------- ----------
         1          1 x          1          3
         1          1 x          1          3
         1          1 x          1          3
         2          1 y          1          5
         2          1 y          1          5
         3          1 z          1          9
         3          1 z          1          9
         3          1 z          1          9
         3          1 z          1          9
 
9 rows selected
這次分組正確了。
從這裡可以知道,是因為partition by或order by列表中指定了decode函式所致。

好了,到現在為止,可以得出是decode引起的分組錯誤。那擴充套件一下,如果是其他函式會不會出現這個錯誤呢,這次我改用nvl:
SQL> select dense_rank() over (order by a, nvl(b,'1'), c) groupid, a, b, c, cntall
  2    from (select a, b, c, count(*)over(order by a, nvl(b,'1'), c) cntall
  3            from abcccc) t
  4  /
 
   GROUPID          A B          C     CNTALL
---------- ---------- - ---------- ----------
         1          1 x          1          3
         1          1 x          1          3
         1          1 x          1          3
         1          1 y          1          5
         1          1 y          1          5
         1          1 z          1          9
         1          1 z          1          9
         1          1 z          1          9
         1          1 z          1          9
 
9 rows selected
結果證明了我的猜測是對的,分析函式中,用於排序或分組的欄位如果存在函式轉換,就可能出現問題。



經過幾次資料測試,最終可以總結出這麼一個規律:
在A欄位相同的前提下,如果B欄位不同,而C欄位相同,且內層查詢中有與外層相同的parition by或order by列表,且列表中存在decode、nvl等轉換的函式
就有可能出現這個問題。如果內層查詢沒有使用相同欄位列表的分析函式,就不會出現此現象:
SQL> select dense_rank() over (order by a, decode(b, 'X', 'Y', b), c) groupid, a, b, c, cntall
  2    from (select a, b, c, 1 cntall
  3            from abcccc) t
  4  /
 
   GROUPID          A B          C     CNTALL
---------- ---------- - ---------- ----------
         1          1 x          1          1
         1          1 x          1          1
         1          1 x          1          1
         2          1 y          1          1
         2          1 y          1          1
         3          1 z          1          1
         3          1 z          1          1
         3          1 z          1          1
         3          1 z          1          1
 
9 rows selected

分析開始說到得兩個語句的執行計劃:
語句一:
SQL> explain plan for select dense_rank() over (order by a, decode(b||'', 'X', 'Y', b), c) groupid, a, b, c, cntall
  2    from (select a, b, c, count(*)over(partition by a, decode(b, 'X', 'Y', b), c) cntall
  3            from abcccc) t
  4  /
 
Explained
 
SQL> select * from table(dbms_xplan.display);
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1435324867
-------------------------------------------------------------------------------
| Id  | Operation                           | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |                    |     9      |   378 |    17  (12)| 00:00:01 |
|   1 |  WINDOW SORT               |                   |     9       |   378 |    17  (12)| 00:00:01 |
|   2 |   VIEW                                  |                   |     9       |   378 |    16   (7)| 00:00:01 |
|   3 |    WINDOW SORT             |                   |     9       |   261 |    16   (7)| 00:00:01 |
|   4 |     TABLE ACCESS FULL | ABCCCC |     9       |   261 |    15   (0)| 00:00:01 |
-------------------------------------------------------------------------------

語句二:
SQL> explain plan for select dense_rank() over (order by a, decode(b, 'X', 'Y', b), c) groupid, a, b, c, cntall
  2    from (select a, b, c, count(*)over(partition by a, decode(b, 'X', 'Y', b), c) cntall
  3            from abcccc) t
  4  /
 
Explained
 
SQL> select * from table(dbms_xplan.display);
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 542054269
-------------------------------------------------------------------------------
| Id  | Operation                          | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |                   |     9      |   378 |    16   (7)| 00:00:01 |
|   1 |  WINDOW NOSORT        |                   |     9       |   378 |    16   (7)| 00:00:01 |
|   2 |   VIEW                                 |                   |     9       |   378 |    16   (7)| 00:00:01 |
|   3 |    WINDOW SORT             |                  |     9       |   261 |    16   (7)| 00:00:01 |
|   4 |     TABLE ACCESS FULL| ABCCCC |     9       |   261 |    15   (0)| 00:00:01 |
-------------------------------------------------------------------------------

可以看到,只是一個簡單的拼接,造成了兩個執行計劃的不同,
一個採用了window sort(正常的這個),一個採用了window nosort(錯誤的這個)
而查詢window nosort相關的資訊,發現了其對應的bug:
6840911 Wrongs results from WINDOW NOSORT in execution plan
目前只有兩種辦法確保不出現此類問題:
1、升級到10.2.0.5及以上版本;
2、改寫語句,改寫方式在我上面的中已經說明了。我採用的方式是這樣的:
select dense_rank() over(order by a, bb, c) groupid, a, b, c, cntall
  from (select a, b, c,
               decode(b, 'X', 'Y', b) bb,
               count(*) over(partition by a, decode(b, 'X', 'Y', b), c) cntall
          from abcccc) t
也就是在內層查詢中提前把欄位轉換好,然後在外層直接使用即可。也可以用語句一的這種方式。

【重要補充】
經過進一步實驗發現,上述結論中的c欄位其實不是必要條件,沒有該欄位的存在仍然會出現這個問題,所以結論改為:
當a欄位相同,b欄位不同時,且內層查詢中有與外層相同的parition by或order by列表,且列表中存在decode、nvl等轉換的函式,就有可能出現這個問題。這樣的話,這個bug的暴露條件又降低了,加大了其出現機率。
查詢如下:
SQL> select dense_rank() over(order by a, decode(b, 'X', 'Y', b)) groupid, a, b, cntall
  2    from (select a, b,
  3                 count(*) over(partition by a, decode(b, 'X', 'Y', b)) cntall
  4            from abcccc) t
  5  /

   GROUPID          A B     CNTALL
----------        ---------- - ----------
         1                    1 x          3
         1                    1 x          3
         1                    1 x          3
         1                    1 y          2
         1                    1 y          2
         1                    1 z          4
         1                    1 z          4
         1                    1 z          4
         1                    1 z          4

9 rows selected

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12932950/viewspace-701661/,如需轉載,請註明出處,否則將追究法律責任。

相關文章