[20151221]sql語句優化.txt

lfree發表於2015-12-21

[20151221]sql語句優化.txt

--自從發現開發亂用distinct以後,連結http://blog.itpub.net/267265/viewspace-1871989/
--我看sql語句特別注意連線多個表,但是顯示僅僅一個表的情況,上個星期五,發現一條:

sql_id=dpdk3xfd6cvky
SELECT EMR_DJMX.ZSFL
    FROM MS_YJ01, L_LIS_SQDMX, EMR_DJMX
   WHERE     MS_YJ01.YJXH IN ( :"SYS_B_00"
                             , :"SYS_B_01"
                             , :"SYS_B_02"
                             , :"SYS_B_03"
                             , :"SYS_B_04"
                             , :"SYS_B_05"
                             , :"SYS_B_06"
                             , :"SYS_B_07"
                             , :"SYS_B_08"
                             , :"SYS_B_09"
                             , :"SYS_B_10"
                             , :"SYS_B_11"
                             , :"SYS_B_12"
                             , :"SYS_B_13"
                             , :"SYS_B_14"
                             , :"SYS_B_15"
                             , :"SYS_B_16"
                             , :"SYS_B_17"
                             , :"SYS_B_18"
                             , :"SYS_B_19")
         AND MS_YJ01.KDRQ >= TO_DATE ( :"SYS_B_20", :"SYS_B_21")
         AND EMR_DJMX.ZSFL IS NOT NULL
         AND EMR_DJMX.ZSFL <> :"SYS_B_22"
         AND EMR_DJMX.XMID = L_LIS_SQDMX.PREHYID
         AND MS_YJ01.SQID = L_LIS_SQDMX.DOCTREQUESTNO
         AND (MS_YJ01.FPHM IS NULL OR MS_YJ01.FPHM = :"SYS_B_23")
         AND MS_YJ01.ZFPB = :"SYS_B_24"
         AND MS_YJ01.ZXPB = :"SYS_B_25"
GROUP BY EMR_DJMX.ZSFL
UNION ALL
SELECT EMR_DJMX.ZSFL
  FROM (SELECT DISTINCT MS_YJ01.sqid
          FROM ms_yj01
         WHERE     MS_YJ01.YJXH IN ( :"SYS_B_26"
                                   , :"SYS_B_27"
                                   , :"SYS_B_28"
                                   , :"SYS_B_29"
                                   , :"SYS_B_30"
                                   , :"SYS_B_31"
                                   , :"SYS_B_32"
                                   , :"SYS_B_33"
                                   , :"SYS_B_34"
                                   , :"SYS_B_35"
                                   , :"SYS_B_36"
                                   , :"SYS_B_37"
                                   , :"SYS_B_38"
                                   , :"SYS_B_39"
                                   , :"SYS_B_40"
                                   , :"SYS_B_41"
                                   , :"SYS_B_42"
                                   , :"SYS_B_43"
                                   , :"SYS_B_44"
                                   , :"SYS_B_45")
               AND MS_YJ01.KDRQ >= TO_DATE ( :"SYS_B_46", :"SYS_B_47")
               AND MS_YJ01.ZFPB = :"SYS_B_48"
               AND MS_YJ01.ZXPB = :"SYS_B_49"
               AND (MS_YJ01.FPHM IS NULL OR MS_YJ01.FPHM = :"SYS_B_50"))
      ,L_LIS_SQDMX
      ,EMR_DJMX
WHERE     EMR_DJMX.ZSFL = :"SYS_B_51"
       AND EMR_DJMX.XMID = L_LIS_SQDMX.PREHYID
       AND SQID = L_LIS_SQDMX.DOCTREQUESTNO;

--僅僅關注union all上部分,下面一段也一樣。為了測試方便,我帶入引數改寫如下:

SELECT  EMR_DJMX.ZSFL
    FROM MS_YJ01,
            L_LIS_SQDMX,
            EMR_DJMX
   WHERE MS_YJ01.YJXH in ('10591737','10591736') and
            MS_YJ01.KDRQ >= TO_DATE('19-12-2015 00:00:00','DD-MM-YYYY HH24:MI:SS') AND
            EMR_DJMX.ZSFL is not null AND
            EMR_DJMX.ZSFL <>9 AND
            EMR_DJMX.XMID = L_LIS_SQDMX.PREHYID AND
            MS_YJ01.SQID = L_LIS_SQDMX.DOCTREQUESTNO AND
         ( MS_YJ01.FPHM is null or MS_YJ01.FPHM = 'NULL' ) and
         MS_YJ01.ZFPB = 0 and
         MS_YJ01.ZXPB = 0
   group by EMR_DJMX.ZSFL;

--in 我僅僅寫兩個。
--這個明明是exists的經典應用案例,開發為了避免重複,破天荒的使用group by(這次沒有使用distinct^_^) 。正確的寫法如下:

SELECT EMR_DJMX.ZSFL
  FROM EMR_DJMX
WHERE     EXISTS
              (SELECT 1
                 FROM MS_YJ01, L_LIS_SQDMX
                WHERE     MS_YJ01.YJXH IN ('10591737', '10591736')
                      AND MS_YJ01.KDRQ >=
                             TO_DATE
                             (
                                '19-12-2015 00:00:00'
                               ,'DD-MM-YYYY HH24:MI:SS'
                             )
                      AND EMR_DJMX.XMID = L_LIS_SQDMX.PREHYID
                      AND MS_YJ01.SQID = L_LIS_SQDMX.DOCTREQUESTNO
                      AND (MS_YJ01.FPHM IS NULL OR MS_YJ01.FPHM = 'NULL')
                      AND MS_YJ01.ZFPB = 0
                      AND MS_YJ01.ZXPB = 0)
       AND EMR_DJMX.ZSFL IS NOT NULL
       AND EMR_DJMX.ZSFL <> 9 ;

--但是改寫後執行計劃如下:(補充也許可能出現重複,加入distinct)

Plan hash value: 2311536074
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name                     | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |                          |      1 |        |       |    11 (100)|          |      0 |00:00:00.01 |    7108 |       |       |          |
|   1 |  HASH GROUP BY                  |                          |      1 |      1 |    51 |    11  (10)| 00:00:01 |      0 |00:00:00.01 |    7108 |  2053K|  2053K|          |
|   2 |   NESTED LOOPS                  |                          |      1 |      1 |    51 |    10   (0)| 00:00:01 |      0 |00:00:00.01 |    7108 |       |       |          |
|   3 |    NESTED LOOPS                 |                          |      1 |      1 |    51 |    10   (0)| 00:00:01 |      0 |00:00:00.01 |    7108 |       |       |          |
|   4 |     NESTED LOOPS                |                          |      1 |      1 |    44 |     8   (0)| 00:00:01 |      0 |00:00:00.01 |    7108 |       |       |          |
|*  5 |      TABLE ACCESS BY INDEX ROWID| MS_YJ01                  |      1 |      1 |    33 |     5   (0)| 00:00:01 |      0 |00:00:00.01 |    7108 |       |       |          |
|*  6 |       INDEX RANGE SCAN          | IDX_MS_YJ01_KDRQ         |      1 |      2 |       |     3   (0)| 00:00:01 |  12647 |00:00:00.01 |     104 |  1025K|  1025K|          |
|   7 |      TABLE ACCESS BY INDEX ROWID| L_LIS_SQDMX              |      0 |      5 |    55 |     3   (0)| 00:00:01 |      0 |00:00:00.01 |       0 |       |       |          |
|*  8 |       INDEX RANGE SCAN          | IF_L_LIS_SQDMX_DOCREQ_NO |      0 |      5 |       |     2   (0)| 00:00:01 |      0 |00:00:00.01 |       0 |  1025K|  1025K|          |
|*  9 |     INDEX RANGE SCAN            | I_EMR_DJMX_XMID          |      0 |      1 |       |     1   (0)| 00:00:01 |      0 |00:00:00.01 |       0 |  1025K|  1025K|          |
|* 10 |    TABLE ACCESS BY INDEX ROWID  | EMR_DJMX                 |      0 |      1 |     7 |     2   (0)| 00:00:01 |      0 |00:00:00.01 |       0 |       |       |          |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
   1 - SEL$1
   5 - SEL$1 / MS_YJ01@SEL$1
   6 - SEL$1 / MS_YJ01@SEL$1
   7 - SEL$1 / L_LIS_SQDMX@SEL$1
   8 - SEL$1 / L_LIS_SQDMX@SEL$1
   9 - SEL$1 / EMR_DJMX@SEL$1
  10 - SEL$1 / EMR_DJMX@SEL$1
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - filter(("MS_YJ01"."SQID" IS NOT NULL AND ("MS_YJ01"."FPHM" IS NULL OR "MS_YJ01"."FPHM"='NULL') AND "MS_YJ01"."ZFPB"=0 AND "MS_YJ01"."ZXPB"=0 AND
              INTERNAL_FUNCTION("MS_YJ01"."YJXH")))
   6 - access("MS_YJ01"."KDRQ">=TO_DATE(' 2015-12-19 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
   8 - access("MS_YJ01"."SQID"="L_LIS_SQDMX"."SYS_NC00021$")
   9 - access("EMR_DJMX"."XMID"="L_LIS_SQDMX"."PREHYID")
  10 - filter(("EMR_DJMX"."ZSFL" IS NOT NULL AND "EMR_DJMX"."ZSFL"<>9))

--邏輯讀並沒有明顯減少。我才發現oracle錯誤了選擇了索引IDX_MS_YJ01_KDRQ。實際上"MS_YJ01"."YJXH"是主鍵,應該選擇這個索引。
--問題在於這個表很長時間沒有分析(2015/11/23號分析過1次),
--oracle錯誤的認為("MS_YJ01"."KDRQ">=TO_DATE(' 2015-12-19 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))的記錄很少。選擇該索引。

--重新分析後問題執行如下:

Plan hash value: 2730452773
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name                     | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |                          |      1 |        |       |    12 (100)|          |      0 |00:00:00.01 |       7 |       |       |          |
|   1 |  NESTED LOOPS                     |                          |      1 |      1 |    20 |    12   (9)| 00:00:01 |      0 |00:00:00.01 |       7 |       |       |          |
|   2 |   NESTED LOOPS                    |                          |      1 |      1 |    20 |    12   (9)| 00:00:01 |      0 |00:00:00.01 |       7 |       |       |          |
|   3 |    VIEW                           | VW_SQ_1                  |      1 |      1 |    13 |     9   (0)| 00:00:01 |      0 |00:00:00.01 |       7 |       |       |          |
|   4 |     HASH UNIQUE                   |                          |      1 |      1 |    44 |            |          |      0 |00:00:00.01 |       7 |  2124K|  2124K|          |
|   5 |      NESTED LOOPS                 |                          |      1 |      1 |    44 |     9   (0)| 00:00:01 |      0 |00:00:00.01 |       7 |       |       |          |
|   6 |       INLIST ITERATOR             |                          |      1 |        |       |            |          |      0 |00:00:00.01 |       7 |       |       |          |
|*  7 |        TABLE ACCESS BY INDEX ROWID| MS_YJ01                  |      2 |      1 |    33 |     6   (0)| 00:00:01 |      0 |00:00:00.01 |       7 |       |       |          |
|*  8 |         INDEX UNIQUE SCAN         | PK_MS_YJ01               |      2 |      2 |       |     4   (0)| 00:00:01 |      2 |00:00:00.01 |       6 |  1025K|  1025K|          |
|   9 |       TABLE ACCESS BY INDEX ROWID | L_LIS_SQDMX              |      0 |      5 |    55 |     3   (0)| 00:00:01 |      0 |00:00:00.01 |       0 |       |       |          |
|* 10 |        INDEX RANGE SCAN           | IF_L_LIS_SQDMX_DOCREQ_NO |      0 |      5 |       |     2   (0)| 00:00:01 |      0 |00:00:00.01 |       0 |  1025K|  1025K|          |
|* 11 |    INDEX RANGE SCAN               | I_EMR_DJMX_XMID          |      0 |      1 |       |     1   (0)| 00:00:01 |      0 |00:00:00.01 |       0 |  1025K|  1025K|          |
|* 12 |   TABLE ACCESS BY INDEX ROWID     | EMR_DJMX                 |      0 |      1 |     7 |     2   (0)| 00:00:01 |      0 |00:00:00.01 |       0 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

--像這種語句優化存在一定的不穩定性,我聽過一些人建議刪除列MS_YJ01.KDRQ的統計資訊。
--隨手建立一個指令碼清除日期欄位資訊:
$ cat del_date_stat.sql
prompt del_date_stat.sql owner table_name column_name
DECLARE
   distcnt   NUMBER;
   density   NUMBER;
   nullcnt   NUMBER;
   srec      DBMS_STATS.statrec;
   avgclen   NUMBER;
BEGIN
   DBMS_STATS.get_column_stats
   (
      ownname   => '&&1'
     ,tabname   => '&&2'
     ,colname   => '&&3'
     ,distcnt   => distcnt
     ,density   => density
     ,nullcnt   => nullcnt
     ,srec      => srec
     ,avgclen   => avgclen
   );
   DBMS_STATS.delete_column_stats
   (
      ownname   => '&&1'
     ,tabname   => '&&2'
     ,colname   => '&&3'
   );
   DBMS_STATS.set_column_stats
   (
      ownname   => '&&1'
     ,tabname   => '&&2'
     ,colname   => '&&3'
     ,distcnt   => distcnt
     ,density   => density
     ,nullcnt   => nullcnt
     ,srec      => NULL
     ,avgclen   => avgclen
   );
END;
/

--通過以上處理後發現邏輯讀明顯下降到XX。即使in裡面很多邏輯讀最大180.(而原來基本3XXXX多)

SYSTEM@192.168.99.105:1521/dbcn> @tab_lh portal_his MS_YJ01 KDRQ

DISPLAY TABLE_NAME OF COLUMN_NAME INFORMATION.
INPUT   OWNER TABLE_NAME COLUMN
SAMPLE  : @ TAB_LH TABLE_NAME [COLUMN_NAME]
IF NOT INPUT COLUMN_NAME ,USE "" .
COLUMN_NAME  DATA_TYPE  DATA_LENGTH N NUM_DISTINCT    DENSITY SAMPLE_SIZE TRANS_LOW            TRANS_HIGH            NUM_NULLS NUM_BUCKETS LAST_ANALYZED       HISTOGRAM DATA_DEFAULT
------------ ---------- ----------- - ------------ ---------- ----------- -------------------- -------------------- ---------- ----------- ------------------- --------- -------------
KDRQ         DATE                 7 Y      4620980 2.1640E-07     7871866 2012-06-22 01:20:31  2015-12-21 08:43:36           0           1 2015-12-21 08:45:45 NONE
1 row selected.^_^!!!

--按照上面清除日期的最大與最小值後如下:

SYSTEM@192.168.99.105:1521/dbcn> @tab_lh portal_his MS_YJ01 KDRQ
DISPLAY TABLE_NAME OF COLUMN_NAME INFORMATION.
INPUT   OWNER TABLE_NAME COLUMN
SAMPLE  : @ TAB_LH TABLE_NAME [COLUMN_NAME]
IF NOT INPUT COLUMN_NAME ,USE "" .

COLUMN_NAME  DATA_TYPE  DATA_LENGTH N NUM_DISTINCT    DENSITY SAMPLE_SIZE TRANS_LOW            TRANS_HIGH            NUM_NULLS NUM_BUCKETS LAST_ANALYZED       HISTOGRAM DATA_DEFAULT
------------ ---------- ----------- - ------------ ---------- ----------- -------------------- -------------------- ---------- ----------- ------------------- --------- ------------
KDRQ         DATE                 7 Y      4620980 2.1640E-07             -- ::                -- ::                         0           1 2015-12-21 09:21:15 NONE

1 row selected.^_^!!!

--以後如何控制呢?自己還給仔細考慮,不過開發確實在編寫sql語句上存在問題,我基本可以猜測把需要的表列出來寫出連線條件,發現
--重複,不是distinct,就是使用group by避免重複。難道不會用exists嗎?

--我自己也在思考這個問題,如果我沒有即使發現,團隊的其他人應該發現這種情況,我已經發現開發存在這個毛病,人家寫好的語句,
--根本不考慮合理不合理,直接借用過來。

--我沒什麼沒及時發現?我給自己的理由是我自己出現點總是從第3方的角度考慮優化,這些不外乎建立索引,使用sql profile以及spm
--來穩定執行計劃。而沒有上來考慮sql語句的執行效率與演算法,有點失策。。。

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

相關文章