首页 > 代码库 > 一次分页SQL优化

一次分页SQL优化

开发说业务有个统计跑不出结果,我让他把sql给我,他说不清楚,那我就直接才消耗时间最长了sql了,查找sql如下

SELECT inst_id,
       sql_id,
       sql_fulltext,
       round(exec_time / 1000000, 0) / 60 exec_time
  FROM (SELECT inst_id,
               sql_id,
               sql_fulltext,
               exec_time,
               rank() over(ORDER BY exec_time DESC) exec_rank
          FROM (SELECT sql_id,
                       sql_fulltext,
                       inst_id,
                       cpu_time,
                       elapsed_time,
                       executions,
                       round(elapsed_time / executions, 0) exec_time
                  FROM gv$sql
                 WHERE executions > 1))
 WHERE exec_rank <= 10;
 
因为我对业务比较了解,因此直接就找出到了这个sql
 
 从sql_fulltext得到sql
select 
id,
agent_account,
operatorAccount,
aisleName,
interface_id,
customer_number,
serialnumber,
customer_serialnumber,
type,
money,
create_date,
status,
remark,
processed,
calls,
upNum,
area,
  reversal_type,
  brokerage_type,
  payment_type,
  refund_type,
  activities
from (
select 
temp.id,
temp.agent_account,
temp.operatorAccount,
      temp.aisleName,
      temp.interface_id,
      temp.customer_number,
      temp.serialnumber,
      temp.customer_serialnumber,
      temp.type,
      temp.money,
      temp.create_date,
      temp.status,
      temp.remark,
      temp.processed,
      temp.calls,
      temp.upNum,
      temp.area,
        temp.reversal_type,
        temp.brokerage_type,
        temp.payment_type,
        temp.refund_type,
        temp.activities,
      rownum row_id
    from (
    select 
      a.id,
      a.agent_account,
      b.login_name as operatorAccount,
      c.aislename as aisleName,
      a.interface_id,
      a.customer_number,
      a.serialnumber,
      a.customer_serialnumber,
      a.type,
      a.money,
      a.create_date,
      a.status,
      a.remark,
      a.processed,
      a.calls,
      a.upNum,
      a.area,
      a.reversal_type,
        a.brokerage_type,
        a.payment_type,
        a.refund_type,
        a.activities
    from 
      tb_recharge a left join tb_operator b on a.operator_id = b.id 
      left join tb_aisle c on a.aisle_id = c.id
     WHERE a.agent_id in (select id from tb_agent where sales_id =  :1)
      and a.status = :2
    order by a.id desc )temp
    where  rownum <= :3 ) where  row_id > :4

    

 

 查询绑定变量的值


 select inst_id, value_string from gv$sql_bind_capture where sql_id = ‘0rhttycv0upqc‘ and inst_id = 1


 得到的值为 103585 1 10 0

 

sql已经成功找出来了,是一个分页语句,这种sql我们首先想到的是order by 列一定要有index,而且执行计划应该走nestloop join 才比较快。


下面来看看执行计划

 

Plan hash value: 3945838093
----------------------------------------------------------------------------------------------------------------------------
| Id  | Operation  | Name   | Rows  | Bytes | Cost (%CPU)| Time   | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |   | 4 |  4052 | 92723   (1)| 00:18:33 |   |   |
|*  1 |  VIEW  |   | 4 |  4052 | 92723   (1)| 00:18:33 |   |   |
|*  2 |   COUNT STOPKEY   |   |   |   ||   |   |   |
|   3 |    VIEW   |   | 4 |  4000 | 92723   (1)| 00:18:33 |   |   |
|*  4 |     SORT ORDER BY STOPKEY  |   | 4 |   900 | 92723   (1)| 00:18:33 |   |   |
|   5 |      NESTED LOOPS OUTER   |   | 4 |   900 | 92722   (1)| 00:18:33 |   |   |
|*  6 |       HASH JOIN OUTER  |   | 4 |   832 | 92718   (1)| 00:18:33 |   |   |
|*  7 |        HASH JOIN  |   | 4 |   720 | 92715   (1)| 00:18:33 |   |   |
|*  8 | TABLE ACCESS FULL  | TB_AGENT   | 3 |30 |   206   (0)| 00:00:03 |   |   |
|   9 | PARTITION RANGE ALL  |   |  1194 |   198K| 92509   (1)| 00:18:31 | 1 |19 |
|  10 |  TABLE ACCESS BY LOCAL INDEX ROWID| TB_RECHARGE    |  1194 |   198K| 92509   (1)| 00:18:31 | 1 |19 |
|* 11 |   INDEX SKIP SCAN  | TB_RECHARGE_I3 |  1194 |   | 91316   (1)| 00:18:16 | 1 |19 |
|  12 |        TABLE ACCESS FULL  | TB_AISLE   |16 |   448 | 3   (0)| 00:00:01 |   |   |
|  13 |       TABLE ACCESS BY INDEX ROWID  | TB_OPERATOR    | 1 |17 | 1   (0)| 00:00:01 |   |   |
|* 14 |        INDEX UNIQUE SCAN  | TB_OPERATOR_PK | 1 |   | 0   (0)| 00:00:01 |   |   |
----------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("ROW_ID">0)
   2 - filter(ROWNUM<=10)
   4 - filter(ROWNUM<=10)
   6 - access("A"."AISLE_ID"="C"."ID"(+))
   7 - access("A"."AGENT_ID"="ID")
   8 - filter("SALES_ID"=103585)
  11 - access("A"."STATUS"=1)
       filter("A"."STATUS"=1)
  14 - access("A"."OPERATOR_ID"="B"."ID"(+))

 

分析:执行计划id 为 7 8  9三行,8和9 hashjoin后返回4行数据,这个显然不准,至少也应该有1000多行(我查了下实际数据有70万行),而且id为9 使用index skip scan,

然后hash join 的结果集作为驱动表与TB_OPERATOR nestloops join 这尼玛能出结果才怪,解决思路如下


先看tb_recharge的索引,status在TB_RECHARGE_I3组合索引的第五个字段

SELECT index_name, column_name, table_name, COLUMN_POSITION
  FROM user_ind_columns
 WHERE table_name = ‘TB_RECHARGE‘
 
INDEX_NAME       COLUMN_NAME TABLE_NAMECOLUMN_POSITION
------------------------------ ------------------------- ------------------------------ ---------------
TB_RECHARGEBK_PK       ID TB_RECHARGE      1
TB_RECHARGE_UNIQUE2       CUSTOMER_SERIALNUMBER TB_RECHARGE      2
TB_RECHARGE_UNIQUE2       AGENT_ACCOUNT TB_RECHARGE      1
TB_RECHARGE_I3       CUSTOMER_NUMBER TB_RECHARGE      1
TB_RECHARGE_I3       CREATE_DATE TB_RECHARGE      2
TB_RECHARGE_I3       AGENT_ACCOUNT TB_RECHARGE      3
TB_RECHARGE_I3       MONEY TB_RECHARGE      4
TB_RECHARGE_I3       STATUS TB_RECHARGE      5
TB_RECHARGE_I5       CREATE_DATE TB_RECHARGE      1
TB_RECHARGE_UNIQUE1       SERIALNUMBER TB_RECHARGE      1


sql中tb_recharge用到了status 和id字段因此我创建了这个索引

create index idx_tb_recharge_status_id(status,id)


创建索引后执行计划如下

SQL>  select  *from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3852339816
---------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation  | Name      | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     4 |  4052 |  2037(1)| 00:00:25 |       |       |
|*  1 |  VIEW  |      |     4 |  4052 |  2037(1)| 00:00:25 |       |       |
|*  2 |   COUNT STOPKEY   |      |       |       |    |      |       |       |
|   3 |    VIEW   |      |     4 |  4000 |  2037(1)| 00:00:25 |       |       |
|*  4 |     SORT ORDER BY STOPKEY  |      |     4 |   900 |  2037(1)| 00:00:25 |       |       |
|   5 |      NESTED LOOPS OUTER   |      |     4 |   900 |  2036(1)| 00:00:25 |       |       |
|*  6 |       HASH JOIN OUTER  |      |     4 |   832 |  2032(1)| 00:00:25 |       |       |
|*  7 |        HASH JOIN  |      |     4 |   720 |  2029(1)| 00:00:25 |       |       |
|*  8 | TABLE ACCESS FULL  | TB_AGENT      |     3 |    30 |   206(0)| 00:00:03 |       |       |
|   9 | TABLE ACCESS BY GLOBAL INDEX ROWID| TB_RECHARGE       |  1194 |   198K|  1822(0)| 00:00:22 | ROWID | ROWID |
|* 10 |  INDEX RANGE SCAN  | IDX_TB_RECHARGE_STATUS_ID |  1194 |       |    13(0)| 00:00:01 |       |       |
|  11 |        TABLE ACCESS FULL  | TB_AISLE      |    16 |   448 |     3(0)| 00:00:01 |       |       |
|  12 |       TABLE ACCESS BY INDEX ROWID  | TB_OPERATOR       |     1 |    17 |     1(0)| 00:00:01 |       |       |
|* 13 |        INDEX UNIQUE SCAN  | TB_OPERATOR_PK      |     1 |       |     0(0)| 00:00:01 |       |       |
---------------------------------------------------------------------------------------------------------------------------------------



上面这个sql跑10s出结果了,其实这里还是没有走分页的特性,tb_agent.id 是pk,因此可以直接把in改成join,最后又加了hint然其走nl,最后结果如下

SELECT id,
       status,
       agent_account,
       operatorAccount,
       aisleName,
       interface_id,
       customer_number,
       serialnumber,
       customer_serialnumber,
       TYPE,
       money,
       create_date,
       remark,
       processed,
       calls,
       upNum,
       area,
       reversal_type,
       brokerage_type,
       payment_type,
       refund_type,
       activities
  FROM (SELECT temp.id,
               temp.agent_account,
               temp.operatorAccount,
               temp.aisleName,
               temp.interface_id,
               temp.customer_number,
               temp.serialnumber,
               temp.customer_serialnumber,
               temp.type,
               temp.money,
               temp.create_date,
               temp.status,
               temp.remark,
               temp.processed,
               temp.calls,
               temp.upNum,
               temp.area,
               temp.reversal_type,
               temp.brokerage_type,
               temp.payment_type,
               temp.refund_type,
               temp.activities,
               rownum row_id
          FROM (SELECT
                /*+ use_nl(a,ta) use_nl(a,b) use_nl(a,c) leading(a,b,c) */
                 a.id,
                 a.agent_account,
                 b.login_name            AS operatorAccount,
                 c.aislename             AS aisleName,
                 a.interface_id,
                 a.customer_number,
                 a.serialnumber,
                 a.customer_serialnumber,
                 a.type,
                 a.money,
                 a.create_date,
                 a.status,
                 a.remark,
                 a.processed,
                 a.calls,
                 a.upNum,
                 a.area,
                 a.reversal_type,
                 a.brokerage_type,
                 a.payment_type,
                 a.refund_type,
                 a.activities
                  FROM (SELECT id FROM tb_agent WHERE sales_id = 103585) ta
                  INNER JOIN tb_recharge a
                  ON ta.id = a.agent_id
                  LEFT JOIN tb_operator b
                    ON a.operator_id = b.id
                  LEFT JOIN tb_aisle c
                    ON a.aisle_id = c.id
                 WHERE a.status = 1
                 ORDER BY a.id DESC) temp
         WHERE rownum <= 10)
 WHERE row_id > 0
SQL>  select  *from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3946970243
---------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation  | Name      | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     4 |  4052 |  5405(1)| 00:01:05 |       |       |
|*  1 |  VIEW  |      |     4 |  4052 |  5405(1)| 00:01:05 |       |       |
|*  2 |   COUNT STOPKEY   |      |       |       |    |      |       |       |
|   3 |    VIEW   |      |     4 |  4000 |  5405(1)| 00:01:05 |       |       |
|   4 |     NESTED LOOPS  |      |     4 |   900 |  5405(1)| 00:01:05 |       |       |
|   5 |      NESTED LOOPS  |      |  1194 |   900 |  5405(1)| 00:01:05 |       |       |
|   6 |       NESTED LOOPS OUTER  |      |  1194 |   250K|  4211(1)| 00:00:51 |       |       |
|   7 |        NESTED LOOPS OUTER  |      |  1194 |   218K|  3017(1)| 00:00:37 |       |       |
|   8 | TABLE ACCESS BY GLOBAL INDEX ROWID| TB_RECHARGE       |  1194 |   198K|  1822(0)| 00:00:22 | ROWID | ROWID |
|*  9 |  INDEX RANGE SCAN DESCENDING  | IDX_TB_RECHARGE_STATUS_ID |  1194 |       |    13(0)| 00:00:01 |       |       |
|  10 | TABLE ACCESS BY INDEX ROWID  | TB_OPERATOR       |     1 |    17 |     1(0)| 00:00:01 |       |       |
|* 11 |  INDEX UNIQUE SCAN  | TB_OPERATOR_PK      |     1 |       |     0(0)| 00:00:01 |       |       |
|  12 |        TABLE ACCESS BY INDEX ROWID  | TB_AISLE      |     1 |    28 |     1(0)| 00:00:01 |       |       |
|* 13 | INDEX UNIQUE SCAN  | TB_AISLEV_PK      |     1 |       |     0(0)| 00:00:01 |       |       |
|* 14 |       INDEX UNIQUE SCAN   | TB_AGENT_PK       |     1 |       |     0(0)| 00:00:01 |       |       |
|* 15 |      TABLE ACCESS BY INDEX ROWID  | TB_AGENT      |     1 |    10 |     1(0)| 00:00:01 |       |       |
---------------------------------------------------------------------------------------------------------------------------------------

最后ms级别出结果了

 


本文出自 “专注于Oracle性能调优” 博客,谢绝转载!

一次分页SQL优化