记一次SQL性能优化,查询时间从4000ms优化到200ms.

Posted 如果你不按照想的方式去活,那你终将按照活的方式去想

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次SQL性能优化,查询时间从4000ms优化到200ms.相关的知识,希望对你有一定的参考价值。

以下这句SQL是从PLM中获取代办工作流的。没优化前SQL语句执行一次大概4000ms(4秒)。

 1 select ch.change_number changeNumber, f.text changeDetail, u1.last_name creator,
 2 l.value status, to_char(create_date,yyyy-mm-dd hh24:mi:ss) createDate, 
 3 so.signoff_status type 
 4 from agile.change ch 
 5 inner join agile.workflow_process wp on (ch.id=wp.change_id and ch.status=wp.state and wp.changed_by is null)
 6 inner join agile.signoff so on wp.id=so.process_id and so.signoff_status in (0,4) and so.required in(1,5)
 7 inner join agile.agileuser u on u.id=so.user_assigned 
 8 inner join agile.langtable l on (ch.status=l.id and l.type=4450 and l.langid=4)
 9 inner join agile.agileuser u1 on ch.originator=u1.id
10 left join agile.agile_flex f on f.id=ch.id and f.attid=2017
11 where (ch.subclass in (2473549,2473495,1,2475794,2473579,2474897,2473519,2480885,2479577,2473531,2485198,2479783) 
12 or (ch.subclass = 2478248 and l.value = 审批))
13 and u.email =‘zhangsan@kedacom.com

使用autotrace分析sql

1 set autotrace on
2 set timing on

分析结果如下:

Elapsed: 00:00:00.008
Plan hash value: 1883359798
 
------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |                  |    24 |  5280 |   954   (1)| 00:00:12 |
|   1 |  NESTED LOOPS OUTER               |                  |    24 |  5280 |   954   (1)| 00:00:12 |
|   2 |   NESTED LOOPS                    |                  |    22 |  3564 |   921   (1)| 00:00:12 |
|   3 |    NESTED LOOPS                   |                  |    22 |  3234 |   910   (1)| 00:00:11 |
|   4 |     NESTED LOOPS                  |                  |    56 |  6328 |   882   (1)| 00:00:11 |
|   5 |      NESTED LOOPS                 |                  |   156 | 11076 |   804   (1)| 00:00:10 |
|   6 |       NESTED LOOPS                |                  |   212 | 10176 |   592   (1)| 00:00:08 |
|*  7 |        TABLE ACCESS FULL          | AGILEUSER        |     1 |    29 |   168   (1)| 00:00:03 |
|*  8 |        TABLE ACCESS BY INDEX ROWID| SIGNOFF          |   210 |  3990 |   424   (0)| 00:00:06 |
|*  9 |         INDEX RANGE SCAN          | SIGNOFF_IDX4     |  1120 |       |     3   (0)| 00:00:01 |
|* 10 |       TABLE ACCESS BY INDEX ROWID | WORKFLOW_PROCESS |     1 |    23 |     1   (0)| 00:00:01 |
|* 11 |        INDEX UNIQUE SCAN          | WF_PROCESS_PK    |     1 |       |     1   (0)| 00:00:01 |
|* 12 |      TABLE ACCESS BY INDEX ROWID  | CHANGE           |     1 |    42 |     1   (0)| 00:00:01 |
|* 13 |       INDEX UNIQUE SCAN           | CHANGE_PK        |     1 |       |     1   (0)| 00:00:01 |
|* 14 |     TABLE ACCESS BY INDEX ROWID   | LANGTABLE        |     1 |    34 |     1   (0)| 00:00:01 |
|* 15 |      INDEX UNIQUE SCAN            | LANGTABLE_PK     |     1 |       |     1   (0)| 00:00:01 |
|  16 |    TABLE ACCESS BY INDEX ROWID    | AGILEUSER        |     1 |    15 |     1   (0)| 00:00:01 |
|* 17 |     INDEX UNIQUE SCAN             | AGILEUSER_PK     |     1 |       |     1   (0)| 00:00:01 |
|  18 |   TABLE ACCESS BY INDEX ROWID     | AGILE_FLEX       |     1 |    58 |     2   (0)| 00:00:01 |
|* 19 |    INDEX RANGE SCAN               | AGILE_FLEX_UQ    |     1 |       |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   7 - filter("U"."EMAIL"=[email protected])
   8 - filter(("SO"."REQUIRED"=1 OR "SO"."REQUIRED"=5) AND ("SO"."SIGNOFF_STATUS"=0 OR 
              "SO"."SIGNOFF_STATUS"=4))
   9 - access("U"."ID"="SO"."USER_ASSIGNED")
  10 - filter("WP"."CHANGED_BY" IS NULL)
  11 - access("WP"."ID"="SO"."PROCESS_ID")
  12 - filter(("CH"."SUBCLASS"=1 OR "CH"."SUBCLASS"=2473495 OR "CH"."SUBCLASS"=2473519 OR 
              "CH"."SUBCLASS"=2473531 OR "CH"."SUBCLASS"=2473549 OR "CH"."SUBCLASS"=2473579 OR 
              "CH"."SUBCLASS"=2474897 OR "CH"."SUBCLASS"=2475794 OR "CH"."SUBCLASS"=2478248 OR 
              "CH"."SUBCLASS"=2479577 OR "CH"."SUBCLASS"=2479783 OR "CH"."SUBCLASS"=2480885 OR 
              "CH"."SUBCLASS"=2485198) AND "CH"."STATUS"="WP"."STATE")
  13 - access("CH"."ID"="WP"."CHANGE_ID")
  14 - filter("CH"."SUBCLASS"=1 OR "CH"."SUBCLASS"=2473495 OR "CH"."SUBCLASS"=2473519 OR 
              "CH"."SUBCLASS"=2473531 OR "CH"."SUBCLASS"=2473549 OR "CH"."SUBCLASS"=2473579 OR 
              "CH"."SUBCLASS"=2474897 OR "CH"."SUBCLASS"=2475794 OR "CH"."SUBCLASS"=2479577 OR 
              "CH"."SUBCLASS"=2479783 OR "CH"."SUBCLASS"=2480885 OR "CH"."SUBCLASS"=2485198 OR 
              "CH"."SUBCLASS"=2478248 AND "L"."VALUE"=审批)
  15 - access("L"."LANGID"=4 AND "L"."TYPE"=4450 AND "CH"."STATUS"="L"."ID")
  17 - access("CH"."ORIGINATOR"="U1"."ID")
  19 - access("F"."ID"(+)="CH"."ID" AND "F"."ATTID"(+)=2017)
       filter("F"."ATTID"(+)=2017)

   Statistics
-----------------------------------------------------------
               1  DB time
               1  Requests to/from client
               2  non-idle wait count
               1  opened cursors cumulative
               1  opened cursors current
               1  pinned cursors current
           58008  session uga memory
               2  user calls

从解释计划中可以看出有2个地方预估时间很长,一个是对agileuser用户表,另一个是signoff,用户审批表。

用户表总共也就几千条记录,而且邮箱还是唯一的,所以资源耗费不是很大。而signoff表数据基数相当庞大,达到百万级。

虽然已经走了索引了,但是走的索引是审批用户的ID这个字段。对于绝大部分人来说,其下的数据不是很多。所以查询起来不会很慢。

但对于某些重要领导(大部分审批流都要他批的那种)数据量就会变得相当庞大。而这个SQL执行慢也恰好是遇到了这样的领导才会慢。

经过具体问题,根据某领导张三的用户ID可以在这张表中查询到2万多数据。也就是说数据库引擎根据现有索引找到该用户的记录后,还要从2万多记录中找到当前需要审批的记录。

分析至此,考虑将用户id以及sql语句中另外的2个条件加入组合索引。重新使用autotrace分析语句,结果如下:

Elapsed: 00:00:00.008
Plan hash value: 441713506
 
-------------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |                  |    24 |  5280 |   622   (1)| 00:00:08 |
|   1 |  NESTED LOOPS OUTER                |                  |    24 |  5280 |   622   (1)| 00:00:08 |
|   2 |   NESTED LOOPS                     |                  |    22 |  3564 |   589   (1)| 00:00:08 |
|   3 |    NESTED LOOPS                    |                  |    22 |  3234 |   578   (1)| 00:00:07 |
|   4 |     NESTED LOOPS                   |                  |    56 |  6328 |   550   (1)| 00:00:07 |
|   5 |      NESTED LOOPS                  |                  |   156 | 11076 |   472   (1)| 00:00:06 |
|   6 |       NESTED LOOPS                 |                  |   212 | 10176 |   260   (1)| 00:00:04 |
|*  7 |        TABLE ACCESS FULL           | AGILEUSER        |     1 |    29 |   168   (1)| 00:00:03 |
|   8 |        INLIST ITERATOR             |                  |       |       |            |          |
|   9 |         TABLE ACCESS BY INDEX ROWID| SIGNOFF          |   210 |  3990 |    93   (2)| 00:00:02 |
|* 10 |          INDEX RANGE SCAN          | SIGNOFF_IDX_KD_1 |   210 |       |     3  (34)| 00:00:01 |
|* 11 |       TABLE ACCESS BY INDEX ROWID  | WORKFLOW_PROCESS |     1 |    23 |     1   (0)| 00:00:01 |
|* 12 |        INDEX UNIQUE SCAN           | WF_PROCESS_PK    |     1 |       |     1   (0)| 00:00:01 |
|* 13 |      TABLE ACCESS BY INDEX ROWID   | CHANGE           |     1 |    42 |     1   (0)| 00:00:01 |
|* 14 |       INDEX UNIQUE SCAN            | CHANGE_PK        |     1 |       |     1   (0)| 00:00:01 |
|* 15 |     TABLE ACCESS BY INDEX ROWID    | LANGTABLE        |     1 |    34 |     1   (0)| 00:00:01 |
|* 16 |      INDEX UNIQUE SCAN             | LANGTABLE_PK     |     1 |       |     1   (0)| 00:00:01 |
|  17 |    TABLE ACCESS BY INDEX ROWID     | AGILEUSER        |     1 |    15 |     1   (0)| 00:00:01 |
|* 18 |     INDEX UNIQUE SCAN              | AGILEUSER_PK     |     1 |       |     1   (0)| 00:00:01 |
|  19 |   TABLE ACCESS BY INDEX ROWID      | AGILE_FLEX       |     1 |    58 |     2   (0)| 00:00:01 |
|* 20 |    INDEX RANGE SCAN                | AGILE_FLEX_UQ    |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   7 - filter("U"."EMAIL"=[email protected])
  10 - access("U"."ID"="SO"."USER_ASSIGNED" AND ("SO"."SIGNOFF_STATUS"=0 OR 
              "SO"."SIGNOFF_STATUS"=4) AND ("SO"."REQUIRED"=1 OR "SO"."REQUIRED"=5))
  11 - filter("WP"."CHANGED_BY" IS NULL)
  12 - access("WP"."ID"="SO"."PROCESS_ID")
  13 - filter(("CH"."SUBCLASS"=1 OR "CH"."SUBCLASS"=2473495 OR "CH"."SUBCLASS"=2473519 OR 
              "CH"."SUBCLASS"=2473531 OR "CH"."SUBCLASS"=2473549 OR "CH"."SUBCLASS"=2473579 OR 
              "CH"."SUBCLASS"=2474897 OR "CH"."SUBCLASS"=2475794 OR "CH"."SUBCLASS"=2478248 OR 
              "CH"."SUBCLASS"=2479577 OR "CH"."SUBCLASS"=2479783 OR "CH"."SUBCLASS"=2480885 OR 
              "CH"."SUBCLASS"=2485198) AND "CH"."STATUS"="WP"."STATE")
  14 - access("CH"."ID"="WP"."CHANGE_ID")
  15 - filter("CH"."SUBCLASS"=1 OR "CH"."SUBCLASS"=2473495 OR "CH"."SUBCLASS"=2473519 OR 
              "CH"."SUBCLASS"=2473531 OR "CH"."SUBCLASS"=2473549 OR "CH"."SUBCLASS"=2473579 OR 
              "CH"."SUBCLASS"=2474897 OR "CH"."SUBCLASS"=2475794 OR "CH"."SUBCLASS"=2479577 OR 
              "CH"."SUBCLASS"=2479783 OR "CH"."SUBCLASS"=2480885 OR "CH"."SUBCLASS"=2485198 OR 
              "CH"."SUBCLASS"=2478248 AND "L"."VALUE"=审批)
  16 - access("L"."LANGID"=4 AND "L"."TYPE"=4450 AND "CH"."STATUS"="L"."ID")
  18 - access("CH"."ORIGINATOR"="U1"."ID")
  20 - access("F"."ID"(+)="CH"."ID" AND "F"."ATTID"(+)=2017)
       filter("F"."ATTID"(+)=2017)

   Statistics
-----------------------------------------------------------
               1  CPU used by this session
               1  Requests to/from client
               2  non-idle wait count
               1  opened cursors cumulative
               1  opened cursors current
               1  pinned cursors current
               2  user calls
>>Query Run In:查询结果 3

从上面的结果可以看出,sql走入了新建的组合索引(SIGNOFF_IDX_KD_1),预估执行时间从原来的06秒变成02秒,cost也大幅度下降,整句SQL的执行时间也从4秒多下降到了200ms。

由此可见,索引对于查询的优化真是显而易见。

以上是关于记一次SQL性能优化,查询时间从4000ms优化到200ms.的主要内容,如果未能解决你的问题,请参考以下文章

记一次mysql性能优化过程

记一次查询性能优化,原30s+,现0.5s~20s

一次SQL查询优化原理分析(900W+数据,从17s到300ms)

记一次SQL优化。

一次SQL查询优化原理分析(900W+数据,从17s到300ms)

记一次生产SQL强势优化