通过执行计划中的CONCATENATION分析sql问题

简介: 昨天开发的一个同事找到我,说写了一条sql语句,但是执行了半个小时还没有执行完,想让我帮忙看看是怎么回事。 他大体上给我讲了下逻辑,表bl1_rc_rates是千万级数据量的表,autsu_subscriber 是个临时表,里面只有三百多条数据,bl1_activity_history 表的数据量略小,是百万级的。
昨天开发的一个同事找到我,说写了一条sql语句,但是执行了半个小时还没有执行完,想让我帮忙看看是怎么回事。
他大体上给我讲了下逻辑,表bl1_rc_rates是千万级数据量的表, autsu_subscriber 是个临时表,里面只有三百多条数据,bl1_activity_history 表的数据量略小,是百万级的。
   select distinct hist.entity_id, rc.* from bl1_activity_history hist, bl1_rc_rates rc, autsu_subscriber sub
where hist.entity_id = sub.subscriber_no
    and hist.customer_id = sub.customer_id
    and hist.activity_id = '48'
    and hist.entity_id = rc.service_receiver_id
    and hist.customer_id = rc.receiver_customer
    and rc.service_receiver_id=sub.subscriber_no
   and rc.receiver_customer= sub.customer_id
   and trunc(hist.activity_date,'dd') = trunc(rc.effective_date,'dd')
and rc.amount > 0
  and rc.expiration_date is null or rc.expiration_date > to_date('20141019','yyyymmdd');

先来看看执行计划吧,一看吓一跳
Plan hash value: 3128694621
--------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                               | Name                     | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |                          |     22G|    13T|       |  4217M  (1)| 999:59:59 |       |       |
|   1 |  HASH UNIQUE                            |                          |    22G|    13T|     16T|  4217M  (1)|999:59:59 |       |       |
|   2 |   CONCATENATION                         |                          |       |       |       |            |          |       |       |
|   3 |    NESTED LOOPS                         |                          |    21G|    13T|       |    47M  (1)|159:35:59 |       |       |
|   4 |     NESTED LOOPS                        |                          |    13M|  8211M|       |  1393K  (1)| 04:38:47 |       |       |
|   5 |      PARTITION RANGE ALL                |                          |     1 |   622 |       |   980K  (1)| 03:16:02 |     1 |    11 |
|*  6 |       TABLE ACCESS FULL                 | BL1_RC_RATES             |     1 |   622 |       |   980K  (1)| 03:16:02 |     1 |    11 |
|   7 |      PARTITION RANGE ALL                |                          |    27M|   622M|       |   413K  (1)| 01:22:45 |     1 |    11 |
|   8 |       TABLE ACCESS FULL                 | BL1_ACTIVITY_HISTORY     |    27M|   622M|       |   413K  (1)| 01:22:45 |     1 |    11 |
|   9 |     TABLE ACCESS FULL                   | AUTSU_SUBSCRIBER         |  1634 | 42484 |       |     3   (0)| 00:00:01 |       |       |
|  10 |    NESTED LOOPS                         |                          |       |       |       |            |          |       |       |
|  11 |     NESTED LOOPS                        |                          |     1 |   672 |       |  2949   (1)| 00:00:36 |       |       |
|  12 |      NESTED LOOPS                       |                          |     1 |    50 |       |  2947   (1)| 00:00:36 |       |       |
|  13 |       TABLE ACCESS FULL                 | AUTSU_SUBSCRIBER         |  1634 | 42484 |       |     5   (0)| 00:00:01 |       |       |
|  14 |       PARTITION RANGE ALL               |                          |     1 |    24 |       |     2   (0)| 00:00:01 |     1 |    11 |
|* 15 |        TABLE ACCESS BY LOCAL INDEX ROWID| BL1_ACTIVITY_HISTORY     |     1 |    24 |       |     2   (0)| 00:00:01 |     1 |    11 |
|* 16 |         INDEX RANGE SCAN                | BL1_ACTIVITY_HISTORY_3IX |     1 |       |       |     2   (0)| 00:00:01 |     1 |    11 |
|  17 |      PARTITION RANGE ALL                |                          |     1 |       |       |     2   (0)| 00:00:01 |     1 |    11 |
|* 18 |       INDEX RANGE SCAN                  | BL1_RC_RATES_3IX         |     1 |       |       |     2   (0)| 00:00:01 |     1 |    11 |
|* 19 |     TABLE ACCESS BY LOCAL INDEX ROWID   | BL1_RC_RATES             |     1 |   622 |       |     2   (0)| 00:00:01 |     1 |     1 |
--------------------------------------------------------------------------------------------------------------------------------------------

这条语句的预计结果又22G rows,执行时间已经没法估量了。这种问题一看就是一个很好的案例。
首先就是查看是不是逻辑上出现了明显的问题,这个时候索引的影响已经没那么重要了。

我们来推敲一下where中的过滤条件
    hist.entity_id = sub.subscriber_no
    and hist.customer_id = sub.customer_id
    and hist.entity_id = rc.service_receiver_id
    and
    and rc.service_receiver_id=sub.subscriber_no
   and rc.receiver_customer= sub.customer_id

通过hist. entity_id = sub.subscriber_no和and rc.service_receiver_id=sub.subscriber_no可以推得hist.entity_id=rc.service_receiver_id,在过滤条件中又写了一遍,
同理hist.customer_id  = sub.customer_id和rc.receiver_customer= sub.customer_id可以推得 hist.customer_id = rc.receiver_customer 所以这个条件也是冗余的。
我们可以基于表中的数据量来合理的选择列的关联。
除了这个问题,还有一个明显的问题,就是查询输出列select distinct hist.entity_id, 
既然hist.entity_id和rc.subscriber_no已经是相等的了,就不需要再输出hist.entity_id然后做distinct运算了。为了突出这个问题的严重性,我先不删除冗余的过滤条件。只是删除查询输出列中的distinct hist.entity_id
select rc.* from bl1_activity_history hist, bl1_rc_rates rc, autsu_subscriber sub
where hist.entity_id = sub.subscriber_no
     and hist.customer_id = sub.customer_id
and hist.activity_id = '48'
    and hist.entity_id = rc.service_receiver_id
    and hist.customer_id = rc.receiver_customer
    and rc.service_receiver_id=sub.subscriber_no
    and rc.receiver_customer= sub.customer_id
  and trunc(hist.activity_date,'dd') = trunc(rc.effective_date,'dd')
  and rc.amount > 0
and rc.expiration_date is null or rc.expiration_date > to_date('20141019','yyyymmdd'); 
来看看执行计划
Plan hash value: 1018700604
-----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name                     | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                          |    21G|    13T|    47M  (1)|159:36:35 |       |       |
|   1 |  CONCATENATION                         |                          |       |       |            |          |       |       |
|   2 |   NESTED LOOPS                         |                          |    21G|    13T|    47M  (1)|159:35:59 |       |       |
|   3 |    NESTED LOOPS                        |                          |    13M|  8211M|  1393K  (1)| 04:38:47 |       |       |
|   4 |     PARTITION RANGE ALL                |                          |     1 |   622 |   980K  (1)| 03:16:02 |     1 |    11 |
|*  5 |      TABLE ACCESS FULL                 | BL1_RC_RATES             |     1 |   622 |   980K  (1)| 03:16:02 |     1 |    11 |
|   6 |     PARTITION RANGE ALL                |                          |    27M|   622M|   413K  (1)| 01:22:45 |     1 |    11 |
|   7 |      TABLE ACCESS FULL                 | BL1_ACTIVITY_HISTORY     |    27M|   622M|   413K  (1)| 01:22:45 |     1 |    11 |
|   8 |    TABLE ACCESS FULL                   | AUTSU_SUBSCRIBER         |  1634 | 42484 |     3   (0)| 00:00:01 |       |       |
|   9 |   NESTED LOOPS                         |                          |       |       |            |          |       |       |
|  10 |    NESTED LOOPS                        |                          |     1 |   672 |  2949   (1)| 00:00:36 |       |       |
|  11 |     NESTED LOOPS                       |                          |     1 |    50 |  2947   (1)| 00:00:36 |       |       |
|  12 |      TABLE ACCESS FULL                 | AUTSU_SUBSCRIBER         |  1634 | 42484 |     5   (0)| 00:00:01 |       |       |
|  13 |      PARTITION RANGE ALL               |                          |     1 |    24 |     2   (0)| 00:00:01 |     1 |    11 |
|* 14 |       TABLE ACCESS BY LOCAL INDEX ROWID| BL1_ACTIVITY_HISTORY     |     1 |    24 |     2   (0)| 00:00:01 |     1 |    11 |
|* 15 |        INDEX RANGE SCAN                | BL1_ACTIVITY_HISTORY_3IX |     1 |       |     2   (0)| 00:00:01 |     1 |    11 |
|  16 |     PARTITION RANGE ALL                |                          |     1 |       |     2   (0)| 00:00:01 |     1 |    11 |
|* 17 |      INDEX RANGE SCAN                  | BL1_RC_RATES_3IX         |     1 |       |     2   (0)| 00:00:01 |     1 |    11 |
|* 18 |    TABLE ACCESS BY LOCAL INDEX ROWID   | BL1_RC_RATES             |     1 |   622 |     2   (0)| 00:00:01 |     1 |     1 |
-----------------------------------------------------------------------------------------------------------------------------------

情况相对改善了不少,但是还是有问题的节奏。
这个时候我们来看看执行计划吧,注意到这个执行计划有些奇怪,只有3个表的关联,但是执行计划中缺出现了两个子查询,对于执行计划中的CONCATENATION自己比较陌生,就没有细究。
直接看走一个并行,效果怎么样。并行开了4个。从执行计划来看,情况好了很多,看似可以完成的样子了。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                               | Name                     | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |    TQ  |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |                          |  1282 |   831K|   390K  (1)| 01:18:07 |       |       |        |      |        |
|   1 |  CONCATENATION                          |                          |       |       |            |          |       |       |        |      |        |
|   2 |   PX COORDINATOR                        |                          |       |       |            |          |       |       |        |      |        |
|   3 |    PX SEND QC (RANDOM)                  | :TQ10000                 |   801 |   519K|   271K  (1)| 00:54:23 |       |       |  Q1,00 | P->S | QC (RAND)       |
|   4 |     NESTED LOOPS                        |                          |   801 |   519K|   271K  (1)| 00:54:23 |       |       |  Q1,00 | PCWP |        |
|   5 |      NESTED LOOPS                       |                          |     1 |   638 |   271K  (1)| 00:54:23 |       |       |  Q1,00 | PCWP |        |
|   6 |       PX BLOCK ITERATOR                 |                          |     1 |   622 |   271K  (1)| 00:54:23 |     1 |    11 |  Q1,00 | PCWC |        |
|*  7 |        TABLE ACCESS FULL                | BL1_RC_RATES             |     1 |   622 |   271K  (1)| 00:54:23 |     1 |    11 |  Q1,00 | PCWP |        |
|   8 |       PARTITION RANGE ALL               |                          |     1 |    16 |     2   (0)| 00:00:01 |     1 |    11 |  Q1,00 | PCWP |        |
|*  9 |        TABLE ACCESS BY LOCAL INDEX ROWID| BL1_ACTIVITY_HISTORY     |     1 |    16 |     2   (0)| 00:00:01 |     1 |    11 |  Q1,00 | PCWP |        |
|* 10 |         INDEX RANGE SCAN                | BL1_ACTIVITY_HISTORY_3IX |     1 |       |     2   (0)| 00:00:01 |     1 |    11 |  Q1,00 | PCWP |        |
|  11 |      TABLE ACCESS FULL                  | AUTSU_SUBSCRIBER         |  1634 | 42484 |     5   (0)| 00:00:01 |       |       |  Q1,00 | PCWP |        |
|  12 |   NESTED LOOPS                          |                          |   481 |   311K|   118K  (1)| 00:23:45 |       |       |        |      |        |
|  13 |    NESTED LOOPS                         |                          |     1 |   648 |  3764   (1)| 00:00:46 |       |       |        |      |        |
|  14 |     TABLE ACCESS FULL                   | AUTSU_SUBSCRIBER         |  1634 | 42484 |     5   (0)| 00:00:01 |       |       |        |      |        |
|  15 |     PARTITION RANGE ALL                 |                          |     1 |   622 |     2   (0)| 00:00:01 |     1 |    11 |        |      |        |
|* 16 |      TABLE ACCESS BY LOCAL INDEX ROWID  | BL1_RC_RATES             |     1 |   622 |     2   (0)| 00:00:01 |     1 |    11 |        |      |        |
|* 17 |       INDEX RANGE SCAN                  | BL1_RC_RATES_3IX         |    13 |       |     2   (0)| 00:00:01 |     1 |    11 |        |      |        |
|  18 |    PARTITION RANGE ALL                  |                          |    27M|   415M|   413K  (1)| 01:22:45 |     1 |    11 |        |      |        |
|* 19 |     TABLE ACCESS FULL                   | BL1_ACTIVITY_HISTORY     |    27M|   415M|   413K  (1)| 01:22:45 |     1 |    11 |        |      |        |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------

但是根据实际的情况,从300条左右的数据中大表中的索引,查取数据应该也没那么慢。肯定还是什么地方不对劲,就查看了下 CONCATENATION 的解释

concatenation在sql级别和两个hint相关,no_expend, no_concat

no_expand提示的说明是

The NO_EXPAND hint prevents the cost-based optimizer from considering OR-expansion for queries having OR conditions or IN-lists in the WHERE clause. Usually, the optimizer considers using OR expansion and uses this method if it decides that the cost is lower than not using it.

use_concat提示的说明是

The USE_CONCAT hint forces combined OR conditions in the WHERE clause of a query to be transformed into a compound query using the UNION ALL set operator. Generally, this transformation occurs only if the cost of the query using the concatenations is cheaper than the cost without them.

 当我读到第二句的时候,我就恍然明白了。
和开发确认过滤条件and rc.expiration_date is null or rc.expiration_date > to_date('20141019','yyyymmdd');  是不是 期望是 and (rc.expiration_date is null or rc.expiration_date > to_date('20141019','yyyymmdd')); 
他一愣,一想确实是这个道理。剩下的事情就简单了。我们不要并行来看看最终的执行结果。
select rc.* from  bl1_rc_rates rc, autsu_subscriber sub,bl1_activity_history hist
where rc.service_receiver_id=sub.subscriber_no
and rc.receiver_customer= sub.customer_id
and  rc.amount > 0
and (rc.expiration_date is null or rc.expiration_date > to_date('20141019','yyyymmdd'))
and rc.service_receiver_id=hist.entity_id
and rc.receiver_customer=hist.customer_id
and  hist.activity_id = '48'

Plan hash value: 3908327465
----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name                     | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                          |  1634 |  1059K|  6706   (1)| 00:01:21 |       |       |
|   1 |  NESTED LOOPS                         |                          |       |       |            |          |       |       |
|   2 |   NESTED LOOPS                        |                          |  1634 |  1059K|  6706   (1)| 00:01:21 |       |       |
|   3 |    NESTED LOOPS                       |                          |  1634 |  1034K|  3764   (1)| 00:00:46 |       |       |
|   4 |     TABLE ACCESS FULL                 | AUTSU_SUBSCRIBER         |  1634 | 42484 |     5   (0)| 00:00:01 |       |       |
|   5 |     PARTITION RANGE ALL               |                          |     1 |   622 |     2   (0)| 00:00:01 |     1 |    11 |
|*  6 |      TABLE ACCESS BY LOCAL INDEX ROWID| BL1_RC_RATES             |     1 |   622 |     2   (0)| 00:00:01 |     1 |    11 |
|*  7 |       INDEX RANGE SCAN                | BL1_RC_RATES_3IX         |    13 |       |     2   (0)| 00:00:01 |     1 |    11 |
|   8 |    PARTITION RANGE ALL                |                          |     1 |       |     2   (0)| 00:00:01 |     1 |    11 |
|*  9 |     INDEX RANGE SCAN                  | BL1_ACTIVITY_HISTORY_3IX |     1 |       |     2   (0)| 00:00:01 |     1 |    11 |
|* 10 |   TABLE ACCESS BY LOCAL INDEX ROWID   | BL1_ACTIVITY_HISTORY     |     1 |    16 |     2   (0)| 00:00:01 |     1 |     1 |
----------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   6 - filter("RC"."AMOUNT">0 AND ("RC"."EXPIRATION_DATE" IS NULL OR "RC"."EXPIRATION_DATE">TO_DATE(' 2014-10-19
              00:00:00', 'syyyy-mm-dd hh24:mi:ss')) AND "RC"."RECEIVER_CUSTOMER"="SUB"."CUSTOMER_ID")
   7 - access("RC"."SERVICE_RECEIVER_ID"="SUB"."SUBSCRIBER_NO")
   9 - access("RC"."SERVICE_RECEIVER_ID"="HIST"."ENTITY_ID" AND "HIST"."ACTIVITY_ID"='48')
  10 - filter("RC"."RECEIVER_CUSTOMER"="HIST"."CUSTOMER_ID")

看来任何细小对的问题都会导致很严重的问题,尤其是在数据量太大的情况下,错误的放大效应就会很明显。

目录
相关文章
|
SQL 数据可视化 关系型数据库
MCP与PolarDB集成技术分析:降低SQL门槛与简化数据可视化流程的机制解析
阿里云PolarDB与MCP协议融合,打造“自然语言即分析”的新范式。通过云原生数据库与标准化AI接口协同,实现零代码、分钟级从数据到可视化洞察,打破技术壁垒,提升分析效率99%,推动企业数据能力普惠化。
1047 3
|
SQL 关系型数据库 MySQL
MySQL进阶突击系列(07) 她气鼓鼓递来一条SQL | 怎么看执行计划、SQL怎么优化?
在日常研发工作当中,系统性能优化,从大的方面来看主要涉及基础平台优化、业务系统性能优化、数据库优化。面对数据库优化,除了DBA在集群性能、服务器调优需要投入精力,我们研发需要负责业务SQL执行优化。当业务数据量达到一定规模后,SQL执行效率可能就会出现瓶颈,影响系统业务响应。掌握如何判断SQL执行慢、以及如何分析SQL执行计划、优化SQL的技能,在工作中解决SQL性能问题显得非常关键。
|
SQL 关系型数据库 MySQL
凌晨2点报警群炸了:一条sql 执行200秒!搞定之后,我总结了一个慢SQL查询、定位分析解决的完整套路
凌晨2点报警群炸了:一条sql 执行200秒!搞定之后,我总结了一个慢SQL查询、定位分析解决的完整套路
凌晨2点报警群炸了:一条sql 执行200秒!搞定之后,我总结了一个慢SQL查询、定位分析解决的完整套路
|
SQL 算法 数据挖掘
【SQL周周练】:利用行车轨迹分析犯罪分子作案地点
【SQL破案系列】第一篇: 如果监控摄像头拍下了很多车辆的行车轨迹,那么如何利用这些行车轨迹来分析车辆运行的特征,是不是能够分析出犯罪分子“踩点”的位置
492 15
【YashanDB知识库】使用leading hint调整SQL执行计划后报错YAS-04522 invalid hint leading
【YashanDB知识库】使用leading hint调整SQL执行计划后报错YAS-04522 invalid hint leading
【YashanDB知识库】使用leading hint调整SQL执行计划后报错YAS-04522 invalid hint leading
|
SQL 关系型数据库 MySQL
【MySQL】SQL分析的几种方法
以上就是SQL分析的几种方法。需要注意的是,这些方法并不是孤立的,而是相互关联的。在实际的SQL分析中,我们通常需要结合使用这些方法,才能找出最佳的优化策略。同时,SQL分析也需要对数据库管理系统,数据,业务需求有深入的理解,这需要时间和经验的积累。
583 12
【YashanDB 知识库】使用 leading hint 调整 SQL 执行计划后报错 YAS-04522 invalid hint leading
在 YashanDB 的所有版本中,使用 leading hint 调整 SQL 执行计划时可能出现“YAS-04522 invalid hint leading”错误,导致 SQL 无法正常执行。原因是 YashanDB 优化器的 Bug。解决方法为避免使用 leading hint。可通过创建测试表 a、b、c 并执行特定 SQL 语句来验证问题是否存在。
|
SQL 数据库 开发者
ClkLog埋点分析系统支持自定义SQL 查询
本期主要为大家介绍ClkLog九月上线的新功能-自定义SQL查询。
ClkLog埋点分析系统支持自定义SQL 查询
|
SQL 存储 关系型数据库
【YashanDB知识库】如何从内存中获取SQL语句的执行计划
【YashanDB知识库】如何从内存中获取SQL语句的执行计划
【YashanDB知识库】过期统计信息导致SQL执行计划变差
【YashanDB知识库】过期统计信息导致SQL执行计划变差