记一次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.的主要内容,如果未能解决你的问题,请参考以下文章
一次SQL查询优化原理分析(900W+数据,从17s到300ms)