在一个风和日丽阳光明媚的早上,刚到公司屁股还没有坐下,就接到报告:XX系统一条SQL执行效率很低,赶紧上去经过一番犀利操作。SQL信息如下:PLAN_TABLE_OUTPUT------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID  dkusf44y9g1yv, child number 0-------------------------------------SELECT A.NO_ID,A.BILL_ID1 FROM MM_USER_INFO A, MM_ATOBSTESTSTATE_INFO BWHERE A.NO_ID=B.NO_ID AND B.DONETIMEB.RUN_IDNO='W' AND A.KKK_SERVICE_ID='1111'Plan hash value: 3801554394------------------------------------------------------------------------------------------------------------------------------| Id  | Operation                           | Name                   | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |------------------------------------------------------------------------------------------------------------------------------|   0 | SELECT STATEMENT                    |                        |       |       |       | 64561 (100)|          |       |       ||*  1 |  HASH JOIN                          |                        |   255K|    11M|  9744K| 64561   (2)| 00:12:55 |       |       ||   2 |   TABLE ACCESS BY GLOBAL INDEX ROWID| MM_USER_INFO           |   255K|  6741K|       | 26342   (1)| 00:05:17 | ROWID | ROWID ||*  3 |    INDEX RANGE SCAN                 | IDX_MMUSER_SERVICEID   |   255K|       |       |   649   (1)| 00:00:08 |       |       ||   4 |   PARTITION RANGE ALL               |                        |   475K|  8821K|       | 37038   (2)| 00:07:25 |     1 |    17 ||*  5 |    TABLE ACCESS FULL                | MM_ATOBSTESTSTATE_INFO |   475K|  8821K|       | 37038   (2)| 00:07:25 |     1 |    17 |--------------------------------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):---------------------------------------------------   1 - access("A"."NO_ID"="B"."NO_ID")   3 - access("A"."KKK_SERVICE_ID"='1111')   5 - filter(("B"."RUN_IDNO"='W' AND "B"."DONETIME"26 rows selected.  从执行计划看出,这条语句走HASH JOIN,第五步全表扫描正常。主要慢在ID=2和ID=5,如果要优化,由于第五步返回结果较少,可以建立RUN_IDNO,DONETIME的分区索引。索引创建如下:CREATE INDEX "OPERTIADM"."IDX1_MM_ATOBSTESTSTATE_INFO" ON "OPERTIADM"."MM_ATOBSTESTSTATE_INFO" ("RUN_IDNO","DONETIME") LOCAL TABLESPACE "TBS_IDX_TKO" parallel 16; alter index "OPERTIADM"."IDX1_MM_ATOBSTESTSTATE_INFO" noparallel; 执行计划如下,竟然没有改变:Execution Plan----------------------------------------------------------Plan hash value: 3801554394--------------------------------------------------------------------------------------------------------------------------------------| Id  | Operation                           | Name                   | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |--------------------------------------------------------------------------------------------------------------------------------------|   0 | SELECT STATEMENT                    |                        |   255K|    11M|       | 64561   (2)| 00:12:55 |       |       ||*  1 |  HASH JOIN                          |                        |   255K|    11M|  9744K| 64561   (2)| 00:12:55 |       |       ||   2 |   TABLE ACCESS BY GLOBAL INDEX ROWID| MM_USER_INFO           |   255K|  6741K|       | 26342   (1)| 00:05:17 | ROWID | ROWID ||*  3 |    INDEX RANGE SCAN                 | IDX_MMUSER_SERVICEID   |   255K|       |       |   649   (1)| 00:00:08 |       |       ||   4 |   PARTITION RANGE ALL               |                        |   475K|  8821K|       | 37038   (2)| 00:07:25 |     1 |    17 ||*  5 |    TABLE ACCESS FULL                | MM_ATOBSTESTSTATE_INFO |   475K|  8821K|       | 37038   (2)| 00:07:25 |     1 |    17 |--------------------------------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):---------------------------------------------------   1 - access("A"."NO_ID"="B"."NO_ID")   3 - access("A"."KKK_SERVICE_ID"='1111')   5 - filter("B"."RUN_IDNO"='W' AND "B"."DONETIME"   执行计划未变,主要是ID=5的cardinality估算不准确。SQL> select count(*) from MM_ATOBSTESTSTATE_INFO  2  where DONETIME  COUNT(*)----------  14539090SQL> select/*+parallel(16)*/ count(*) from MM_ATOBSTESTSTATE_INFO  2  where RUN_IDNO = 'W';  COUNT(*)----------     59675 既然cardinality不准确,那么就收集统计信息,收集完统计信息执行计划如下:Plan hash value: 1403561594---------------------------------------------------------------------------------------------------------------------------------------| Id  | Operation                            | Name                        | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |---------------------------------------------------------------------------------------------------------------------------------------|   0 | SELECT STATEMENT                     |                             |      0 |        |      0 |00:00:00.01 |       0 |      0 ||*  1 |  TABLE ACCESS BY LOCAL INDEX ROWID   | MM_USER_INFO                |      1 |      1 |    842 |00:00:10.81 |     302K|      5 ||   2 |   NESTED LOOPS                       |                             |      1 |   1349K|   1685 |00:00:10.81 |     301K|      5 ||   3 |    PARTITION RANGE SUBQUERY          |                             |      1 |   1349K|    842 |00:00:10.80 |     300K|      5 ||   4 |     TABLE ACCESS BY LOCAL INDEX ROWID| MM_ATOBSTESTSTATE_INFO      |      6 |   1349K|    842 |00:00:00.01 |     842 |      0 ||*  5 |      INDEX RANGE SCAN                | IDX1_MM_ATOBSTESTSTATE_INFO |      6 |   1349K|    842 |00:00:00.01 |      77 |      0 ||   6 |    PARTITION RANGE ITERATOR          |                             |    842 |      1 |    842 |00:00:00.01 |    1741 |      0 ||*  7 |     INDEX UNIQUE SCAN                | PK_MM_USER_INFO             |    842 |      1 |    842 |00:00:00.01 |    1741 |      0 |---------------------------------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):---------------------------------------------------   1 - filter("A"."KKK_SERVICE_ID"='1111')   5 - access("B"."RUN_IDNO"='W' AND "B"."DONETIME"   7 - access("A"."NO_ID"="B"."NO_ID")   现在走NESTED LOOPS了,但是还是需要10s多,效率没有啥提升,通过执行计划可以看出主要慢在 PARTITION RANGE SUBQUERY上,那么只能关闭这个功能对应的参数,当然,最好的是使用SQL PROFILE绑定到如下语句:SELECT/*+leading(b) use_nl(a) index(b IDX1_MM_ATOBSTESTSTATE_INFO)opt_param('_subquery_pruning_enabled' 'false')*/ A.NO_ID, A.BILL_ID1FROM MM_USER_INFO A, MM_ATOBSTESTSTATE_INFO B  WHERE A.NO_ID = B.NO_ID  AND B.DONETIMEAND A.KKK_SERVICE_ID = '1111'  绑定后的执行计划如下:Plan hash value: 4290111086------------------------------------------------------------------------------------------------------------------------------| Id  | Operation                            | Name                        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |------------------------------------------------------------------------------------------------------------------------------|   0 | SELECT STATEMENT                     |                             |      0 |        |      0 |00:00:00.01 |       0 ||*  1 |  TABLE ACCESS BY LOCAL INDEX ROWID   | MM_USER_INFO                |      1 |      1 |    842 |00:00:00.02 |    3436 ||   2 |   NESTED LOOPS                       |                             |      1 |   1349K|   1685 |00:00:00.02 |    2594 ||   3 |    PARTITION RANGE ALL               |                             |      1 |   1349K|    842 |00:00:00.01 |     853 ||   4 |     TABLE ACCESS BY LOCAL INDEX ROWID| MM_ATOBSTESTSTATE_INFO      |     17 |   1349K|    842 |00:00:00.01 |     853 ||*  5 |      INDEX RANGE SCAN                | IDX1_MM_ATOBSTESTSTATE_INFO |     17 |   1349K|    842 |00:00:00.01 |      88 ||   6 |    PARTITION RANGE ITERATOR          |                             |    842 |      1 |    842 |00:00:00.01 |    1741 ||*  7 |     INDEX UNIQUE SCAN                | PK_MM_USER_INFO             |    842 |      1 |    842 |00:00:00.01 |    1741 |------------------------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):---------------------------------------------------   1 - filter("A"."KKK_SERVICE_ID"='1111')   5 - access("B"."RUN_IDNO"='W' AND "B"."DONETIME"   7 - access("A"."NO_ID"="B"."NO_ID")  非常完美,执行效率由原来的10s多降低为0.01s,效率提升上千倍。通过分析真实的执行计划可以快速找到问题的ROOT CAUSE,从而解决之。这里通过分析得知需要建立索引,但是建立索引后执行计划未变,发现是cardinality估算不准,那么收集统计信息,收集完毕后,走索引和NL,但是却出现了PARTITION RANGE SUBQUERY影响效率,那么只能在语句级先关闭这个参数,通过SQL PROFILE绑定从而达到解决问题的目的。附:关于partition的内容可以参考VLDB and Partitioning Guide https://docs.oracle.com/en/database/oracle/oracle-database/19/vldbg/index.html里面的Advanced Partition Pruning Techniques有关于 PARTITION RANGE SUBQUERY的内容。
10-20 04:15
查看更多