优化 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_cases
和stg_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) <> '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_number
、start_date
和 end_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_case
在case_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_Date
和End_Date
的顺序,根据我的经验,我发现范围过滤在索引的下限。
【讨论】:
以上是关于优化 SELECT 查询性能的主要内容,如果未能解决你的问题,请参考以下文章