oracle中distinct和group by的區別

風靈使發表於2018-12-30

其實二者沒有什麼可比性,但是對於不包含聚集函式的GROUP BY操作來說,和DISTINCT操作是等價的。不過雖然二者的結果是一樣的,但是二者的執行計劃並不相同。

Oracle9i中:

SQL> SELECT * FROM V$VERSION;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production PL/SQL Release 9.2.0.4.0 - Production
CORE    9.2.0.3.0       Production
TNS for Linux: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production

SQL> CREATE TABLE T AS SELECT ROWNUM ID, A.* FROM DBA_OBJECTS A;

表已建立。

SQL> CREATE INDEX IND_T_CREATED ON T (CREATED);

索引已建立。

SQL> ALTER TABLE T MODIFY CREATED NOT NULL;

表已更改。

SQL> ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';

會話已更改。

SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T')

PL/SQL 過程已成功完成。

SQL> SET AUTOT ON EXP
SQL> SELECT COUNT(*) FROM (SELECT DISTINCT CREATED FROM T);

  COUNT(*)
----------
4794

執行計劃
----------------------------------------------------------
0      SELECT STATEMENT Optimizer=CHOOSE (Cost=65 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     VIEW (Cost=65 Card=4794)
   3    2       SORT (UNIQUE) (Cost=65 Card=4794 Bytes=38352)
   4    3         INDEX (FAST FULL SCAN) OF 'IND_T_CREATED' (NON-UNIQUE) (Cost=4 Card=41802 Bytes=334416)


SQL> SELECT COUNT(*) FROM (SELECT CREATED FROM T GROUP BY CREATED);

  COUNT(*)
----------
4794

執行計劃
----------------------------------------------------------
0      SELECT STATEMENT Optimizer=CHOOSE (Cost=65 Card=1 Bytes=2)
   1    0   SORT (AGGREGATE)
   2    1     VIEW (Cost=65 Card=4794 Bytes=9588)
   3    2       SORT (GROUP BY) (Cost=65 Card=4794 Bytes=38352)
   4    3         INDEX (FAST FULL SCAN) OF 'IND_T_CREATED' (NON-UNIQUE) (Cost=4 Card=41802 Bytes=334416)

從執行計劃上看,DISTINCT的操作是SORT (UNIQUE),而GROUP BYSORT (GROUP BY)DISTINCT操作只需要找出所有不同的值就可以了。而GROUP BY操作還要為其他聚集函式進行準備工作。從這一點上將,GROUP BY操作做的工作應該比DISTINCT所做的工作要多一些。

除了這一點,基本上看不到DISTINCTGROUP BY(沒有聚集函式的情況)有什麼區別,而且從執行效率上也看不到明顯的差異。

不過從10g開始,二者的差異開始體現出來了。

SQL> CONN YANGTK/YANGTK@YTK已連線。
SQL> SET AUTOT OFF
SQL> SET TIMING OFF
SQL> CREATE TABLE T AS SELECT ROWNUM ID, A.* FROM DBA_OBJECTS A;

表已建立。

SQL> CREATE INDEX IND_T_CREATED ON T (CREATED);

索引已建立。

SQL> ALTER TABLE T MODIFY CREATED NOT NULL;

表已更改。

SQL> ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';

會話已更改。

SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T')

PL/SQL 過程已成功完成。

SQL> SET AUTOT ON
SQL> SET TIMING ON

建立好測試環境後,看一看標準分頁函式中,兩個操作的差異:

SQL> SELECT * 
  2  FROM 
  3  (
  4   SELECT ROWNUM RN, A.* 
  5   FROM 
  6   (
  7    SELECT CREATED 
  8    FROM T 
  9    GROUP BY CREATED
10   ) A
11   WHERE ROWNUM < 20
12  ) 
13  WHERE RN >= 10;

        RN CREATED
---------- -------------------
10 2005-12-19 17:07:57
        11 2005-12-19 17:07:58
        12 2005-12-19 17:08:24
        13 2005-12-19 17:08:25
        14 2005-12-19 17:08:26
        15 2005-12-19 17:08:27
        16 2005-12-19 17:08:28
        17 2005-12-19 17:08:29
        18 2005-12-19 17:08:33
        19 2005-12-19 17:08:35

已選擇10行。

已用時間:  00: 00: 00.06

執行計劃
----------------------------------------------------------
Plan hash value: 3639065582

-------------------------------------------------------------------------------
| Id  | Operation                | Name          | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |               |    19 |   418 |     1   (0)|
|*  1 |  VIEW                    |               |    19 |   418 |     1   (0)|
|*  2 |   COUNT STOPKEY          |               |       |       |            |
|   3 |    VIEW                  |               |   969 |  8721 |     1   (0)|
|*  4 |     SORT GROUP BY STOPKEY|               |   969 |  7752 |     1   (0)|
|   5 |      INDEX FULL SCAN     | IND_T_CREATED |   969 |  7752 |     1   (0)|
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("RN">=10)
   2 - filter(ROWNUM<20)
   4 - filter(ROWNUM<20)

統計資訊
----------------------------------------------------------
1  recursive calls
          0  db block gets
         67  consistent gets
          0  physical reads
          0  redo size
        642  bytes sent via SQL*Net to client
        385  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         10  rows processed

SQL> SELECT * 
  2  FROM 
  3  (
  4   SELECT ROWNUM RN, A.* 
  5   FROM 
  6   (
  7    SELECT DISTINCT CREATED 
  8    FROM T 
  9   ) A
10   WHERE ROWNUM < 20
11  ) 
12  WHERE RN >= 10;

        RN CREATED
---------- -------------------
10 2005-12-19 17:07:57
        11 2005-12-19 17:07:58
        12 2005-12-19 17:08:24
        13 2005-12-19 17:08:25
        14 2005-12-19 17:08:26
        15 2005-12-19 17:08:27
        16 2005-12-19 17:08:28
        17 2005-12-19 17:08:29
        18 2005-12-19 17:08:33
        19 2005-12-19 17:08:35

已選擇10行。

已用時間:  00: 00: 00.03

執行計劃
----------------------------------------------------------
Plan hash value: 1650124153

-------------------------------------------------------------------------------
| Id  | Operation                | Name          | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |               |    19 |   418 |    14  (36)|
|*  1 |  VIEW                    |               |    19 |   418 |    14  (36)|
|*  2 |   COUNT STOPKEY          |               |       |       |            |
|   3 |    VIEW                  |               |   987 |  8883 |    14  (36)|
|*  4 |     SORT GROUP BY STOPKEY|               |   987 |  7896 |    14  (36)|
|   5 |      INDEX FAST FULL SCAN| IND_T_CREATED | 50333 |   393K|    10  (10)|
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("RN">=10)
   2 - filter(ROWNUM<20)
   4 - filter(ROWNUM<20)

統計資訊
----------------------------------------------------------
1  recursive calls
          0  db block gets
         73  consistent gets
          0  physical reads
          0  redo size
        642  bytes sent via SQL*Net to client
        385  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         10  rows processed

出乎意料的是,GROUP BY操作的COST更低,而且邏輯讀也小,這似乎與二者的工作量成反比。仔細觀察執行計劃發現,問題的根源來自於GROUP BY使用INDEX FULL SCAN,而DISTINCT使用了INDEX FAST FULL SCAN。也許有人會感到奇怪,索引的快速全掃描不是要比索引全掃描效率更高嗎?對於讀取所有資料的情況下,確實是索引快速全掃效率更高。但是由於這裡採用了分頁,只取前20條資料,而且Oracle的10g增加了GROUP BY STOPKEY這種新的執行路徑,因此在這裡GROUP BY操作的效率更高。

觀察執行計劃中的處理行數可以發現,索引全掃描由於是按照索引的順序掃描,因此利用了STOPKEY,僅僅處理了969條記錄就停了下來。而對於DISTINCT操作的快速索引全速而言,顯然沒有使用STOPKEY,讀取了所有的50333條記錄。這就是GROUP BYDISTINCT的效能差異原因。

相關文章