1. 程式人生 > >oracle_SQL優化_高水位線導致的效能問題

oracle_SQL優化_高水位線導致的效能問題

今天晨會,開發人員說SIT資料庫有一張表,之前查詢很快就能返回結果,現在需要5分鐘才能返回結果,需要協助查詢原因並優化,資料庫版本11.2.0.4 64 bit for linux,SQL語句如下:

1 select * FROM TB_XXXX a where EDIFLAG<>'90' and EDIFLAG<>'99' and SITE_NO='C07' and rownum <=100 and BONDED_AREA='1' ;

這是很簡單的一條SQL,效能下降這麼大,肯定是有問題的,問了開發人員,他們說這張表並不存在DELETE操作,那麼就應該不是碎片的問題導致的。查詢這張表上的索引如下:

1 INDEX_NAME                     COLUMN_NAME
2 ------------------------------ --------------------
3 IX_08_INVC_ID                  INVC_ID
4 IX_TB8_ON                      ORD_NO
5 IX_TF008IED                    IF_ETR_DATE
6 IX_XXXX_EDIFLAG               EDIFLAG
7 PK_IF_008                      ORD_ID

索引還沒少建,看看SQL的執行計劃和統計資訊。

01 SQL> set autotrace TRACEONLY
02 SQL> set lines 200 pages 200
03 SQL> select * FROM TB_XXXX a where EDIFLAG<>'90' and EDIFLAG<>'99' and SITE_NO='C07' and rownum <=100 and BONDED_AREA='1' ;
04
05 no rows selected
06
07 Elapsed: 00:04:12.31
08
09
Execution Plan
10 ----------------------------------------------------------
11 Plan hash value: 2394103272
12
13 -------------------------------------------------------------------------------
14 | Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
15 -------------------------------------------------------------------------------
16 |   0 | SELECT STATEMENT   |          |     3 |   645 |   120K  (1)| 00:24:01 |
17 |*  1 |  COUNT STOPKEY     |          |       |       |            |          |
18 |*  2 |   TABLE ACCESS FULL| TB_XXXX |     3 |   645 |   120K  (1)| 00:24:01 |
19 -------------------------------------------------------------------------------
20
21 Predicate Information (identified by operation id):
22 ---------------------------------------------------
23
24 1 - filter(ROWNUM<=100)
25 2 - filter("EDIFLAG"<>'90' AND "SITE_NO"='C07' AND "BONDED_AREA"='1'
26 AND "EDIFLAG"<>'99')
27
28 Statistics
29 ----------------------------------------------------------
30 1795  recursive calls
31 0  db block gets
32 442185  consistent gets
33 149261  physical reads
34 0  redo size
35 3779  bytes sent via SQL*Net to client
36 481  bytes received via SQL*Net from client
37 1  SQL*Net roundtrips to/from client
38 39  sorts (memory)
39 0  sorts (disk)
40 0  rows processed

SQL執行一次需要的物理讀442185個數據塊,塊大小設定8K,也就是需要物理讀3 .4GB,外帶邏輯讀149261個數據塊,約等於1.1GB,檢視一下這個表的大小和資料量。

01 SQL> select bytes/1024/1024/1024 from user_segments where segment_name='TB_XXXX';
02
03 BYTES/1024/1024/1024
04 --------------------
05 3.375
06 SQL> select count(*) from TB_XXXX;
07
08 COUNT(*)
09 ----------
10 669387

算一下平均每條記錄獎金5.3MB,每條記錄5M是什麼概念?表中難道存在大欄位?查詢結果如下:

01 SQL> desc TB_XXXX                       
02 Name                                                          Null?    Type
03 ------------------------------------------------------------- -------- ----------------
04 ORD_ID                                                        NOT NULL VARCHAR2(30)
05 SITE_NO                                                                VARCHAR2(7)
06 OUTGO_CMD_DATE                                                         DATE
07 DLVER_CD                                                               VARCHAR2(3)
08 CHG_CD                                                                 VARCHAR2(3)
09 ORD_LVL_CD                                                             VARCHAR2(3)
10 DLV_RQST_DATE                                                          DATE
11 RTN_ID                                                                 NUMBER(12)
12 COD_YN                                                                 VARCHAR2(1)
13 CUST_ID                                                                NUMBER(12)
14 INVC_RCVER_NM                                                          VARCHAR2(20)
15 INVC_TELD                                                              VARCHAR2(20)
16 INVC_HP_TELD                                                           VARCHAR2(12)
17 INVC_ZIP_NO                                                            VARCHAR2(6)
18 INVC_ADDR_LRGN                                                         VARCHAR2(30)
19 INVC_ADDR_MRGN                                                         VARCHAR2(30)
20 INVC_ADDR_SRGN                                                         VARCHAR2(30)
21 INVC_ADDR_DTL                                                          VARCHAR2(200)
22 PURCH_CANCEL_NOTICE                                                    VARCHAR2(40)
23 PRSNT_MSG                                                              VARCHAR2(60)
24 INVC_MSG                                                               VARCHAR2(200)
25 COD_RCV_AMT                                                   NOT NULL NUMBER(15,2)
26 RCPT_GB                                                       NOT NULL VARCHAR2(3)
27 RCPT_SO_ID                                                             NUMBER(7)
28 RCPT_RCVER_NM                                                          VARCHAR2(100)
29 RCPT_ADDR                                                              VARCHAR2(200)
30 IF_ETR_DATE                                                            DATE
31 IF_MDF_DATE                                                            DATE
32 IF_RESULT_DATE                                                         DATE
33 EDIFLAG                                                                VARCHAR2(2)
34 IF_RESULTS                                                             VARCHAR2(200)
35 PVC_ID                                                                 NUMBER(7)
36 LOCAL_ID                                                               NUMBER(7)
37 COUTY_ID                                                               NUMBER(7)
38 MEDI_LCLSS_ID                                                          NUMBER(7)
39 CONTACT_2                                                              VARCHAR2(120)
40 ORD_AMT                                                                NUMBER(15,2)
41 BORD_ID                                                                VARCHAR2(40)
42 ORD_NO                                                                 VARCHAR2(20)
43 PAY_TYPE_CD                                                            VARCHAR2(2)
44 ADDR_T                                                                 NUMBER(1)
45 INCLU_VALUABLES                                                        NUMBER(1)
46 BL_ORDER_NO_OLD                                                        VARCHAR2(40)
47 CASES_ID                                                               NUMBER(11)
48 COD_FLAG                                                               VARCHAR2(2)
49 CASH                                                                   NUMBER(15,2)
50 ARCHIVE_FLAG                                                           NUMBER(2)
51 CHNL_ID                                                                VARCHAR2(2)
52 BONDED_AREA                                                            VARCHAR2(8)
53 INVC_ID                                                                VARCHAR2(30)
54 ADDR_ID                                                                NUMBER(11)
55 SN_GUID                                                                VARCHAR2(40)

所有欄位加起來,一條記錄也不到1KB呀,到這裡就可以斷定就是高水位導致的問題。由於是SIT環境,比較隨意,回收下這張表的碎片,看看能回收多少空間。

1 SQL> alter table TB_XXXX move;
2
3 Table altered.
4 SQL> select bytes/1024/1024/1024 from user_segments where segment_name='TB_XXXX';
5
6 BYTES/1024/1024/1024
7 --------------------
8 .248046875

回收完碎片,這張表才250MB,碎片硬把表撐大近15倍,MOVE完之後,索引全部失效,需要rebuild。

1 SQL> select index_name,status from user_indexes where table_name='TB_XXXX';
2
3 INDEX_NAME                     STATUS
4 ------------------------------ --------
5 IX_08_INVC_ID                  UNUSABLE
6 IX_TB8_ON                      UNUSABLE
7 IX_TF008IED                    UNUSABLE
8 IX_XXXX_EDIFLAG                UNUSABLE
9 PK_IF_008                      UNUSABLE

索引重建之後,也縮小不少,不過這一堆索引這個SQL都用不上。

01 SQL> select bytes/1024/1024/1024 from user_segments where segment_name='IX_TB8_ON';
02
03 BYTES/1024/1024/1024
04 --------------------
05 .46484375
06 SQL> alter index IX_TB8_ON rebuild;
07
08 Index altered.
09 SQL> select bytes/1024/1024/1024 from user_segments where segment_name='IX_TB8_ON';
10
11 BYTES/1024/1024/1024
12 --------------------
13 .016601563
14 SQL> select bytes/1024/1024/1024 from user_segments where segment_name='IX_TF008IED';
15
16 BYTES/1024/1024/1024
17 --------------------
18 .25390625
19 SQL> alter index IX_TF008IED rebuild;
20
21 Index altered.
22 SQL> select bytes/1024/1024/1024 from user_segments where segment_name='IX_TF008IED';
23
24 BYTES/1024/1024/1024
25 --------------------
26 .014648438
27 SQL> select bytes/1024/1024/1024 from user_segments where segment_name='IX_IF008_EDIFLAG';
28
29 BYTES/1024/1024/1024
30 --------------------
31 .018554688
32 SQL> alter index IX_IF008_EDIFLAG rebuild;
33
34 Index altered.
35 SQL> select bytes/1024/1024/1024 from user_segments where segment_name='IX_IF008_EDIFLAG';
36
37 BYTES/1024/1024/1024
38 --------------------
39 .010742188
40 SQL> select bytes/1024/1024/1024 from user_segments where segment_name='PK_IF_008';
41
42 BYTES/1024/1024/1024
43 --------------------
44 .569335938
45 SQL> alter index PK_IF_XXXX rebuild;
46
47 Index altered.
48 SQL> select bytes/1024/1024/1024 from user_segments where segment_name='PK_IF_XXXX';
49
50 BYTES/1024/1024/1024
51 --------------------
52 .014648438

回收碎片後,這個SQL執行只需要0.12秒。

1 SQL> select * FROM TB_XXXX a where EDIFLAG<>'90' and EDIFLAG<>'99' and SITE_NO='C07' and rownum <=100 and BONDED_AREA='1' ;
2
3 no rows selected
4
5 Elapsed: 00:00:00.12

再看下執行計劃和統計資訊。

01 SQL> set autotrace traceonly
02 SQL> /
03
04 no rows selected
05
06 Elapsed: 00:00:00.12
07
08 Execution Plan
09 ----------------------------------------------------------
10 Plan hash value: 2394103272
11
12 -------------------------------------------------------------------------------
13 | Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
14 -------------------------------------------------------------------------------
15 |   0 | SELECT STATEMENT   |          |     3 |   645 |   120K  (1)| 00:24:01 |
16 |*  1 |  COUNT STOPKEY     |          |       |       |            |          |
17 |*  2 |   TABLE ACCESS FULL| TB_XXXX  |     3 |   645 |   120K  (1)| 00:24:01 |
18 -------------------------------------------------------------------------------
19
20 Predicate Information (identified by operation id):
21 ---------------------------------------------------
22
23 1 - filter(ROWNUM<=100)
24 2 - filter("EDIFLAG"<>'90' AND "SITE_NO"='C07' AND "BONDED_AREA"='1'
25 AND "EDIFLAG"<>'99')
26
27 Statistics
28 ----------------------------------------------------------
29 0  recursive calls
30 0  db block gets
31 32303  consistent gets
32 0  physical reads
33 0  redo size
34 3779  bytes sent via SQL*Net to client
35 481  bytes received via SQL*Net from client
36 1  SQL*Net roundtrips to/from client
37 0  sorts (memory)
38 0  sorts (disk)
39 0  rows processed

下面在看下這張表的資料分佈,看看哪個列適合建索引。

01 SQL> select distinct BONDED_AREA from TB_XXXX ;
02
03 BONDED_A
04 --------
05 1
06
07 SQL> select distinct EDIFLAG from TB_XXXX ;
08
09 ED
10 --
11 10
12 90
13
14 SQL> select distinct SITE_NO  from TB_XXXX ;
15
16 SITE_NO
17 -------
18 C10
19 C06
20 C81
21 C99
22 C05
23 C07
24 C01
25 C03
26 C04
27
28 9 rows selected.
29
30 SQL> select count(SITE_NO) from TB_XXXX where SITE_NO='C07';
31
32 COUNT(SITE_NO)
33 --------------
34 40674

針對這條SQL,SITE_NO列建立索引最為有效,其他列上的索引都不會被這條SQL用到。

01 SQL> create index ix_SITE_NO on TB_XXXX (SITE_NO);
02
03 Index created.
04
05 SQL> set autotrace on
06 SQL> select * FROM TB_XXXX a where EDIFLAG<>'90' and EDIFLAG<>'99' and SITE_NO='C07' and rownum <=100 and BONDED_AREA='1' ;
07
08 no rows selected
09
10 Elapsed: 00:00:00.07
11
12 Execution Plan
13 ----------------------------------------------------------
14 Plan hash value: 272980480
15
16 -------------------------------------------------------------------------------------------
17 | Id  | Operation                    | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
18 -------------------------------------------------------------------------------------------
19 |   0 | SELECT STATEMENT             |            |     3 |   645 | 11802   (1)| 00:02:22 |
20 |*  1 |  COUNT STOPKEY               |            |       |       |            |          |
21 |*  2 |   TABLE ACCESS BY INDEX ROWID| TB_XXXX   |     3 |   645 | 11802   (1)| 00:02:22 |
22 |*  3 |    INDEX RANGE SCAN          | IX_SITE_NO | 41680 |       |    91   (2)| 00:00:02 |
23 -------------------------------------------------------------------------------------------
24
25 Predicate Information (identified by operation id):
26 ---------------------------------------------------
27
28 1 - filter(ROWNUM<=100)
29 2 - filter("EDIFLAG"<>'90' AND "BONDED_AREA"='1' AND "EDIFLAG"<>'99')
30 3 - access("SITE_NO"='C07')
31
32 Statistics
33 ----------------------------------------------------------
34 0  recursive calls
35 0  db block gets
36 22048  consistent gets