优化 SELECT 查询性能

Posted

技术标签:

【中文标题】优化 SELECT 查询性能【英文标题】:Optimizing SELECT query performance 【发布时间】:2016-12-01 11:21:50 【问题描述】:

我有一个运行非常缓慢的SELECT 语句,它阻碍了我们的夜间进程。

查询是:(请不要评论隐式连接语法,这是由运行此代码的 Informatica 自动生成的):

SELECT *
  FROM STG_DIM_CRM_CASES,V_CRM_CASE_ID_EXISTS_IN_DWH,stg_scd_customers_key
 WHERE STG_DIM_CRM_CASES.CRM_CASE_ID = V_CRM_CASE_ID_EXISTS_IN_DWH.CASE_ID(+)
   AND STG_DIM_CRM_CASES.account_number = stg_scd_customers_key.account_number(+)
   and STG_DIM_CRM_CASES.Case_Create_Date between  stg_scd_customers_key.start_date(+) and  stg_scd_customers_key.end_date(+)

编辑:实际查询仅选择account_number,start_date,end_date 和另一列未编入索引的列。

表格信息:

STG_DIM_CRM_CASES

Index - (Account_Number,Case_Create_Date)
size - 270k records.

stg_scd_customers_key

Index - Account_Number,Start_Date,End_Date
Partitioned - End_Date
Size - 500 million records.

V_CRM_CASE_ID_EXISTS_IN_DWH(查看)-

select  t.case_id
from crm_ps_rc_case t, dim_crm_cases x
where t.case_id=x.crm_case_id;

dim_crm_cases -

Indexed - (crm_case_id)
Size - 100 million .

crm_ps_rc_case -

Size - 270k records

编辑 - 如果不清楚,视图会返回 270k 条记录。

没有连接到stg_scd 的查询需要几秒钟,看起来它是导致性能问题的部分,尽管它正在连接到一个 1 亿条记录的表,但视图也在几秒钟内运行。目前,查询需要 12 到 30 分钟,具体取决于我们的来源有多忙。

这是执行计划:

6   |   0 | SELECT STATEMENT                |                             |  3278K|  1297M|   559K  (4)| 02:10:37 |       |       |        |      |            |
7   |   1 |  PX COORDINATOR                 |                             |       |       |            |          |       |       |        |      |            |
8   |   2 |   PX SEND QC (RANDOM)           | :TQ10003                    |  3278K|  1297M|   559K  (4)| 02:10:37 |       |       |  Q1,03 | P->S | QC (RAND)  |
9   |*  3 |    HASH JOIN OUTER              |                             |  3278K|  1297M|   559K  (4)| 02:10:37 |       |       |  Q1,03 | PCWP |            |
10  |   4 |     PX RECEIVE                  |                             | 29188 |    10M| 50662   (5)| 00:11:50 |       |       |  Q1,03 | PCWP |            |
11  |   5 |      PX SEND HASH               | :TQ10002                    | 29188 |    10M| 50662   (5)| 00:11:50 |       |       |  Q1,02 | P->P | HASH       |
12  |*  6 |       HASH JOIN RIGHT OUTER     |                             | 29188 |    10M| 50662   (5)| 00:11:50 |       |       |  Q1,02 | PCWP |            |
13  |   7 |        BUFFER SORT              |                             |       |       |            |          |       |       |  Q1,02 | PCWC |            |
14  |   8 |         PX RECEIVE              |                             | 29188 |   370K| 50575   (5)| 00:11:49 |       |       |  Q1,02 | PCWP |            |
15  |   9 |          PX SEND BROADCAST      | :TQ10000                    | 29188 |   370K| 50575   (5)| 00:11:49 |       |       |        | S->P | BROADCAST  |
16  |  10 |           VIEW                  | V_CRM_CASE_ID_EXISTS_IN_DWH | 29188 |   370K| 50575   (5)| 00:11:49 |       |       |        |      |            |
17  |* 11 |            HASH JOIN            |                             | 29188 |   399K| 50575   (5)| 00:11:49 |       |       |        |      |            |
18  |  12 |             TABLE ACCESS FULL   | CRM_PS_RC_CASE              | 29188 |   199K|   570   (1)| 00:00:08 |       |       |        |      |            |
19  |  13 |             INDEX FAST FULL SCAN| DIM_CRM_CASES$1PK           |   103M|   692M| 48894   (3)| 00:11:25 |       |       |        |      |            |
20  |  14 |        PX BLOCK ITERATOR        |                             | 29188 |    10M|    87   (2)| 00:00:02 |       |       |  Q1,02 | PCWC |            |
21  |  15 |         TABLE ACCESS FULL       | STG_DIM_CRM_CASES           | 29188 |    10M|    87   (2)| 00:00:02 |       |       |  Q1,02 | PCWP |            |
22  |  16 |     BUFFER SORT                 |                             |       |       |            |          |       |       |  Q1,03 | PCWC |            |
23  |  17 |      PX RECEIVE                 |                             |   515M|    14G|   507K  (3)| 01:58:28 |       |       |  Q1,03 | PCWP |            |
24  |  18 |       PX SEND HASH              | :TQ10001                    |   515M|    14G|   507K  (3)| 01:58:28 |       |       |        | S->P | HASH       |
25  |  19 |        PARTITION RANGE ALL      |                             |   515M|    14G|   507K  (3)| 01:58:28 |     1 |  2982 |        |      |            |
26  |  20 |         TABLE ACCESS FULL       | STG_SCD_CUSTOMERS_KEY       |   515M|    14G|   507K  (3)| 01:58:28 |     1 |  2982 |        |      |            |
27  ------------------------------------------------------------------------------------------------------------------------------------------------------------
28   
29  Predicate Information (identified by operation id):
30  ---------------------------------------------------
31   
32     3 - access("STG_DIM_CRM_CASES"."ACCOUNT_NUMBER"="STG_SCD_CUSTOMERS_KEY"."ACCOUNT_NUMBER"(+))
33         filter("STG_DIM_CRM_CASES"."CASE_CREATE_DATE">="STG_SCD_CUSTOMERS_KEY"."START_DATE"(+) AND 
34                "STG_DIM_CRM_CASES"."CASE_CREATE_DATE"<="STG_SCD_CUSTOMERS_KEY"."END_DATE"(+))
35     6 - access("STG_DIM_CRM_CASES"."CRM_CASE_ID"="V_CRM_CASE_ID_EXISTS_IN_DWH"."CASE_ID"(+))
36    11 - access("T"."CASE_ID"="X"."CRM_CASE_ID")

注意:添加索引可能是个问题,具体取决于索引。这不是使用此表的唯一位置,因此索引可能会干扰这些表上的其他命令(主要是插入)。

我还尝试在 stg_scd 上添加过滤器,并排除所有小于 Table_Cases 中最小日期的日期,但这并没有帮助,因为它只过滤了 1 年的记录。

提前致谢。

【问题讨论】:

我认为索引是你最好的选择。 完整计划?我没有看到 Table_Cases 和 stg_scd。 我还看到至少一个被阻止的操作“缓冲区排序”。如果您发布完整计划,这将很有帮助。可以有不止一棵 DFO 树。 可能我不懂 smt,但在您的解释计划中,我看到了视图 V_Cases_Dwh 和表 CRM_PS_RC_CASE。在您的描述中,我没有看到任何关于表 CRM_PS_RC_CASE 的提及以及此解释计划的查询(您加入 Table_Cases、V_Cases_Dwh、stg_scd 时的查询)? (请在回答时包含@username。上次您由于某种原因忘记这样做了,我记得这个问题工具很晚) 是否有机会(手动或以其他方式)用现代连接语法替换查询? 【参考方案1】:

我会考虑为Table_casesstg_scd 之间的连接创建一个物化视图(使用refresh fast on demand)。我假设连接中的大部分工作都在每天都不会改变的行上。

【讨论】:

我的同事不太喜欢MV。所以我们可以越过名单。 可惜了。也许您可以向他们展示 MV 的 Oracle 文档——特别是关于“MV 的主要用途之一是预先计算昂贵的连接”的部分。他们不喜欢 MV 有什么特别的原因吗? MV 在这里大家都很熟悉,我也不知道具体为什么,我猜他们有他们的原因。过去我已经尝试说服他们使用 MV 会有所帮助,但没有运气:)【参考方案2】:

问题在于扫描所有分区:

18 | PX 发送哈希 | :TQ10001 | 515M| 14G| 507K (3)| 01:58:28 | | | | S->P | 哈希 | 25 | 19 |分区范围全部 | | 515M| 14G| 507K (3)| 01:58:28 | 1 | 2982 | | | | 26 | 20 |表访问完全 | STG_SCD_CUSTOMERS_KEY | 515M| 14G|

发生这种情况是因为您正在使用该表的左连接。您可以使用绑定变量选择 1 个分区吗?什么是分区键? 我没有看到并行的提示,但根据您的计划,它使用并行。在任何对象级别上是否存在并行度?请问可以删除并行并发布没有并行的解释计划吗?

【讨论】:

不,任何地方都没有平行提示.. 我不知道为什么这么说。是的,这是个问题,但我确实需要使用所有分区,至少几乎所有分区。 @sagi 您可以使用如下语句检查对象的并行设置:select degree, dba_tables.* from dba_tables where trim(degree) &lt;&gt; '1';。也有可能在会话级别强制并行,或在系统级别自动执行。您可以使用 /*+ no_parallel */ 之类的提示覆盖并行性。 但是这种可能性不应该加快这个过程吗?所有分区都在并行运行。 @jonheller @sagi 可能,但并行性可能出错的方式有很多。例如,如果索引不是并行的,但表是并行的,Oracle 可能会决定使用并行全表扫描而不是单线程索引访问。也许并行 FTS 快 10%,但使用 64 个线程——查询略有改进,但系统效率极低。如果其他查询也在做同样的事情,它会很快让整个系统陷入困境。意外的并行查询很容易破坏系统 - 如果您找到其中一个,您可能应该检查其他查询。 @sagi 看起来您可能处于没有神奇的访问路径可以在几秒钟内运行的情况。 300K 索引读取可能很昂贵,尤其是在索引聚集因子很高的情况下。 (也就是说,如果数据是随机排序的,因此需要从表中读取 300K 块。)但即使您遇到全表扫描,也可能有提高性能的方法 - 修改 DOP,尝试减少其他系统活动, 检查原始 I/O 读取性能是否存在系统问题等。【参考方案3】:

我认为问题在于视图,我怀疑它正在完全执行并返回所有行条件被应用之前。

视图的整体效果是添加列CASE_ID,如果在其中找到CRM_CASE_ID,则该列不为空,否则为空。我用两个直接连接和一个 CASE 表达式替换了视图。通过用逻辑替换视图的便利性,您可以直接连接到其中的每个表,从而避免一级连接深度。

尝试运行此版本的查询:

SELECT
  a.*, b.*, c.*,
  CASE WHEN t.case_id is not null and X.case_id is not null then t.case_id END CASE_ID
FROM STG_DIM_CRM_CASES a
LEFT JOIN crm_ps_rc_case t
  ON t.case_id = a.CRM_CASE_ID
LEFT JOIN dim_crm_cases x
  ON x.crm_case_id = a.CRM_CASE_ID
LEFT JOIN V_CRM_CASE_ID_EXISTS_IN_DWH b
  ON a.CRM_CASE_ID = b.CASE_ID
LEFT JOIN stg_scd_customers_key c
  ON a.account_number = c.account_number
 and a.Case_Create_Date between c.start_date and  stg_scd_customers_key.end_date

如果您仅将 a.*, b.*, c.* 替换为您实际需要的确切列,您将获得更快的速度,因为返回的数据更少。如果您还为查找的键加上您实际选择的所有列(覆盖索引)添加索引,您将大大加快速度,因为可以使用仅索引访问。

您应该至少验证所有连接到的列中都有索引。

【讨论】:

其实它自己的视图非常快,所以我怀疑这会解决问题,但我会在我开始工作时尝试一下。只选择了需要的列,我只是没有写它们,因为它们无关紧要。感谢您的回答。 @sagi 视图本身可能很快,但可能会使查询的其他部分非常慢,请看我的回答【参考方案4】:

我认为正在发生的事情是引擎必须在应用限制标准之前将 100m+ 记录从视图联接解析为 500m 记录(因此它创建了一个交叉联接,即使它可以使用大量记录的索引来生成然后解析。因此,即使您将其编写为外连接,引擎也无法以这种方式处理它(我不知道为什么)

因此,至少 100m*500m = 50,000m 需要生成大量数据,然后进行解析/限制。

通过消除视图,引擎可能能够更好地优化和使用索引,从而消除对 50,000m 记录连接的需要。

我会集中时间进行故障排除的领域:

消除视图只是为了将其作为潜在的开销问题移除。 发现 stg_scd_customers_key 和 V_CRM_CASE_ID_EXISTS_IN_DWH 之间不存在关联。这意味着引擎可能会在结果之前进行交叉连接 STG_DIM_CRM_CASES 到 stg_scd_customers_key 已解决。

考虑消除视图,或使用内联视图

消除视图:

SELECT *
  FROM STG_DIM_CRM_CASES 
      ,crm_ps_rc_case t
      ,dim_crm_cases x 
      ,stg_scd_customers_key
 WHERE t.case_id=x.crm_case_id
   AND STG_DIM_CRM_CASES.CRM_CASE_ID = t.CASE_ID(+)
   AND STG_DIM_CRM_CASES.account_number = stg_scd_customers_key.account_number(+)
   AND STG_DIM_CRM_CASES.Case_Create_Date 
       between  stg_scd_customers_key.start_date(+) and stg_scd_customers_key.end_date(+)

使用内联视图:

SELECT *
  FROM STG_DIM_CRM_CASES 
  (select  t.case_id
   from crm_ps_rc_case t, dim_crm_cases x
   where t.case_id=x.crm_case_id) V_CRM_CASE_ID_EXISTS_IN_DWH
      ,stg_scd_customers_key
 WHERE STG_DIM_CRM_CASES.CRM_CASE_ID = V_CRM_CASE_ID_EXISTS_IN_DWH.CASE_ID(+)
   AND STG_DIM_CRM_CASES.account_number = stg_scd_customers_key.account_number(+)
   AND STG_DIM_CRM_CASES.Case_Create_Date 
       between  stg_scd_customers_key.start_date(+) and stg_scd_customers_key.end_date(+)

至于为什么: - http://www.dba-oracle.com/art_hints_views.htm

虽然 where 子句的顺序应该不重要,但请考虑:在关闭追逐时,引擎按列出的顺序执行,限制 500m 向下,然后从视图中添加补充数据在逻辑上会更快。

SELECT *
  FROM STG_DIM_CRM_CASES,stg_scd_customers_key,V_CRM_CASE_ID_EXISTS_IN_DWH
 WHERE STG_DIM_CRM_CASES.account_number = stg_scd_customers_key.account_number(+)
   and STG_DIM_CRM_CASES.Case_Create_Date between  stg_scd_customers_key.start_date(+) and  stg_scd_customers_key.end_date(+)
   and STG_DIM_CRM_CASES.CRM_CASE_ID = V_CRM_CASE_ID_EXISTS_IN_DWH.CASE_ID(+)

【讨论】:

我认为我的问题不够清楚,视图返回 270k 记录,我相信优化器在外部查询之前正在处理视图..我会试一试我什么时候去上班。谢谢你的回答。 数学:100m*500m = 50,000mm = 50,000,000,000m(假设m为百万)【参考方案5】:

您的问题是,Oracle 实际上只有两种方法可以从 stg_scd_customers_key 获取所需的行。要么 (A) 它执行该表的单个 FULL SCAN,然后过滤掉它不想要的行,要么 (B) 它执行 270,000 次索引查找,每个查找 3 到 5 个逻辑 I/O(取决于索引的高度),再加上另外 1 个逻辑 I/O 来实际从表中读取块。

鉴于FULL SCAN 提供的多块读取和其他优化,并且根据您的表统计信息,Oracle 的优化器猜测FULL SCAN 会更快。而且很有可能是对的。

您需要做的是给 Oracle 一个更好的选择。

如果你不能在你所在的地方使用物化视图,一个好的“穷人”物化视图就是所谓的覆盖索引。现在,这对您的查询不合理,因为您执行了SELECT *。但是你真的需要stg_scd_customers_key 的每一列吗?

如果您可以减少从 stg_scd_customers_key 获得的列列表,您可以创建一个索引,该索引 (A) 以 account_numberstart_dateend_date 开头,并且 (B) 包括所有其他您需要选择的列。

例如:

SELECT stg_im_crm_cases.*, V_CRM_CASE_ID_EXISTS_IN_DWH.*, stg_scd_customers_key.column_1, stg_scd_customers_key.column_2
  FROM STG_DIM_CRM_CASES,V_CRM_CASE_ID_EXISTS_IN_DWH,stg_scd_customers_key
 WHERE STG_DIM_CRM_CASES.CRM_CASE_ID = V_CRM_CASE_ID_EXISTS_IN_DWH.CASE_ID(+)
   AND STG_DIM_CRM_CASES.account_number = stg_scd_customers_key.account_number(+)
   and STG_DIM_CRM_CASES.Case_Create_Date between  stg_scd_customers_key.start_date(+) and  stg_scd_customers_key.end_date(+)

如果您可以进行查询,并在 stg_scd_customers_key(account_number、start_date、end_date、column_1、column_2)上创建索引,那么您将为 Oracle 提供更好的选择。现在它可以单独读取索引,而不是表。

对于这么大的表,在您尝试之前无法保证。但覆盖索引通常正是医生所要求的。 (当然,所有关于新索引的常见警告都适用)。

【讨论】:

感谢您的回答。我评论了 cmets 中的列选择,我忘了在问题中提及它。我实际上只选择了需要索引的列(account_number、start_date、end_date),而另一列没有。我无法在此特定表上添加索引,因为它正在其他地方使用大量插入.. 选项 C:对涵盖 Account_Number、Start_Date 和 End_Date 的索引进行快速全索引扫描。 ?【参考方案6】:

一些注意事项:

1) INDEXcrm_ps_rc_casecase_id 上没有索引这是一个问题,您正在加入 270k 100m 与 HASH JOIN(不好)

2) 选定的列 视图V_CRM_CASE_ID_EXISTS_IN_DWH 选择t.case_id,但它应该选择x.crm_case_id,至少,直到您不解决t.case_id 的索引。这会将 HASH JOIN 传播到您的所有执行计划中......(不好)

3) 之间 范围联接/过滤始终是一个问题,尤其是在大型表上,但您可以限制在范围上添加条件的问题。让我解释一下,尝试将这些条件添加到您的 WHERE 子句中:

AND stg_scd_customers_key.end_date =  (
       SELECT min(r.end_date)
       FROM stg_scd_customers_key r
       WHERE  r.end_date >= STG_DIM_CRM_CASES.Case_Create_Date
)
AND stg_scd_customers_key.start_date =  (
       SELECT max(r.start_date)
       FROM stg_scd_customers_key r
       WHERE  r.start_date <= STG_DIM_CRM_CASES.Case_Create_Date
)

是的,它会计算 270k * 2 个子查询,但最终的 join 将在更少的 recs 限制 IO 操作上起作用(应该会更好)

4) 索引列顺序 如果确实如此,或者是否无关紧要,则会有相互矛盾的报告,但根据我的经验..确实如此。 这可能只是一个很小的改进,但您可以尝试修改stg_scd_customers_key 上的索引,颠倒Start_DateEnd_Date 的顺序,根据我的经验,我发现范围过滤在索引的下限。

【讨论】:

以上是关于优化 SELECT 查询性能的主要内容,如果未能解决你的问题,请参考以下文章

查询性能优化

如何提高查询性能?

MySQL查询性能优化

Django查询优化之select_related和prefetch_related

MySQL性能优化以及常用命令

mysql之性能优化