Oracle 12c 中的子选择性能不佳
Posted
技术标签:
【中文标题】Oracle 12c 中的子选择性能不佳【英文标题】:Subselect in Oracle 12c with bad performance 【发布时间】:2017-02-27 08:25:50 【问题描述】:从 Oracle 11g 迁移到 12c 后,我们遇到了一个巨大的性能问题,例如以下 select 语句。同样的语句在 11g 中也能正常工作。
表和索引
-- Create table
create table PS_CS_ADRART_TB
(
cs_adressmandant VARCHAR2(5) not null,
cs_person_id VARCHAR2(20) not null,
cs_beziehung_id VARCHAR2(20) not null,
seqnum INTEGER not null,
effdt DATE,
eff_status VARCHAR2(1) not null,
cs_adrart_cd VARCHAR2(20) not null,
cs_adress_id VARCHAR2(20) not null,
cs_kdnr_as400 VARCHAR2(8) not null,
cs_plzgk VARCHAR2(11) not null,
cs_plz_pf VARCHAR2(15) not null,
cs_aendgr_cd VARCHAR2(20) not null,
cs_datasource_cd VARCHAR2(20) not null,
cs_betrag NUMBER(14,4) not null,
cs_belegdat DATE,
cs_adrtyp_xl VARCHAR2(2) not null,
cs_checked VARCHAR2(1) not null,
cs_journal_xl VARCHAR2(4) not null,
address2 VARCHAR2(55) not null,
row_added_dttm TIMESTAMP(6),
row_added_oprid VARCHAR2(30) not null,
row_lastmant_dttm TIMESTAMP(6),
row_lastmant_oprid VARCHAR2(30) not null,
cs_recstat_xl VARCHAR2(4) not null,
cs_update_count NUMBER(10) not null
)
tablespace CS_APP
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 102416K
next 1M
minextents 1
maxextents unlimited
);
-- Create/Recreate indexes
create unique index PSACS_ADRART_TB on PS_CS_ADRART_TB (CS_ADRESSMANDANT, CS_KDNR_AS400, EFFDT)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 20M
next 1M
minextents 1
maxextents unlimited
);
create index PSBCS_ADRART_TB on PS_CS_ADRART_TB (CS_PERSON_ID)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 20M
next 1M
minextents 1
maxextents unlimited
);
create index PSCCS_ADRART_TB on PS_CS_ADRART_TB (CS_BEZIEHUNG_ID)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 20M
next 1M
minextents 1
maxextents unlimited
);
create unique index PS_CS_ADRART_TB on PS_CS_ADRART_TB (CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 20M
next 1M
minextents 1
maxextents unlimited
);
create index PSDCS_ADRART_TB on PS_CS_ADRART_TB (CS_PLZ_PF)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 40K
next 1M
minextents 1
maxextents unlimited
);
create index PS0CS_ADRART_TB on PS_CS_ADRART_TB (CS_ADRESS_ID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 20M
next 1M
minextents 1
maxextents unlimited
);
create index PS1CS_ADRART_TB on PS_CS_ADRART_TB (CS_KDNR_AS400, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 20M
next 1M
minextents 1
maxextents unlimited
);
create index PS2CS_ADRART_TB on PS_CS_ADRART_TB (ROW_ADDED_DTTM, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 40K
next 1M
minextents 1
maxextents unlimited
);
create index PS3CS_ADRART_TB on PS_CS_ADRART_TB (ROW_ADDED_OPRID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 40K
next 1M
minextents 1
maxextents unlimited
);
create index PS4CS_ADRART_TB on PS_CS_ADRART_TB (ROW_LASTMANT_DTTM, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 40K
next 1M
minextents 1
maxextents unlimited
);
create index PS5CS_ADRART_TB on PS_CS_ADRART_TB (ROW_LASTMANT_OPRID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 40K
next 1M
minextents 1
maxextents unlimited
);
create index PS6CS_ADRART_TB on PS_CS_ADRART_TB (CS_RECSTAT_XL, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
tablespace PSINDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 40K
next 1M
minextents 1
maxextents unlimited
);
表格大小
select count(*) from ps_cs_adrart_tb a
--> 41367270
选择语句
SELECT A.CS_ADRESS_ID, A.SEQNUM, TO_CHAR(A.EFFDT, 'YYYY-MM-DD')
from PS_CS_ADRART_TB A
where A.CS_ADRESSMANDANT = '001'
and a.cs_kdnr_as400 = '63916917'
and a.effdt =
(select max(b.effdt)
from ps_cs_adrart_tb b
where b.cs_adressmandant = a.cs_adressmandant
and b.cs_person_id = a.cs_person_id
and b.cs_beziehung_id = a.cs_beziehung_id
and b.seqnum = a.seqnum
and b.effdt <= trunc(sysdate)
)
说明 Oracle 11g 计划
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 89 | 2 | 00:00:01 |
| * 1 | FILTER | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID | PS_CS_ADRART_TB | 1 | 89 | 1 | 00:00:01 |
| * 3 | INDEX RANGE SCAN | PSACS_ADRART_TB | 1 | | 1 | 00:00:01 |
| 4 | SORT AGGREGATE | | 1 | 59 | | |
| 5 | FIRST ROW | | 1 | 59 | 1 | 00:00:01 |
| * 6 | INDEX RANGE SCAN (MIN/MAX) | PS_CS_ADRART_TB | 1 | 59 | 1 | 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter("A"."EFFDT"= (SELECT MAX("B"."EFFDT") FROM "PS_CS_ADRART_TB" "B" WHERE "B"."EFFDT"<=TRUNC(SYSDATE@!) AND "B"."SEQNUM"=:B1 AND "B"."CS_BEZIEHUNG_ID"=:B2 AND "B"."CS_PERSON_ID"=:B3 AND
"B"."CS_ADRESSMANDANT"=:B4))
* 3 - access("A"."CS_ADRESSMANDANT"='001' AND "A"."CS_KDNR_AS400"='63916917')
* 6 - access("B"."CS_ADRESSMANDANT"=:B1 AND "B"."CS_PERSON_ID"=:B2 AND "B"."CS_BEZIEHUNG_ID"=:B3 AND "B"."SEQNUM"=:B4 AND "B"."EFFDT"<=TRUNC(SYSDATE@!))
说明 Oracle 12c 计划
------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 140 | 34366 | 00:00:02 |
| * 1 | HASH JOIN | | 1 | 140 | 34366 | 00:00:02 |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED | PS_CS_ADRART_TB | 1 | 89 | 1 | 00:00:01 |
| * 3 | INDEX RANGE SCAN | PS1CS_ADRART_TB | 1 | | 1 | 00:00:01 |
| 4 | VIEW | VW_SQ_1 | 41889 | 2136339 | 34365 | 00:00:02 |
| * 5 | FILTER | | | | | |
| 6 | HASH GROUP BY | | 41889 | 2471451 | 34365 | 00:00:02 |
| * 7 | INDEX RANGE SCAN | PS_CS_ADRART_TB | 12746381 | 752036479 | 34365 | 00:00:02 |
------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 1 - access("A"."EFFDT"="MAX(B.EFFDT)" AND "ITEM_1"="A"."CS_ADRESSMANDANT" AND "ITEM_2"="A"."CS_PERSON_ID" AND "ITEM_3"="A"."CS_BEZIEHUNG_ID" AND "ITEM_4"="A"."SEQNUM")
* 3 - access("A"."CS_KDNR_AS400"='63916917' AND "A"."CS_ADRESSMANDANT"='001')
* 5 - filter('001'='001')
* 7 - access("B"."CS_ADRESSMANDANT"='001' AND "B"."EFFDT"<=TRUNC(SYSDATE@!))
* 7 - filter("B"."EFFDT"<=TRUNC(SYSDATE@!))
Note
-----
- dynamic sampling used for this statement
如您所见,从索引PS_CS_ADRART_TB
中,只有CS_ADRESSMANDANT
和EFFDT
用于过滤数据,这非常糟糕。
对于以下稍有不同的选择语句,Oracle 12c 使用索引来确定子选择的数据。
改变外部选择的where条件
SELECT A.CS_ADRESS_ID, A.SEQNUM, TO_CHAR(A.EFFDT, 'YYYY-MM-DD')
from PS_CS_ADRART_TB A
where a.cs_kdnr_as400 = '53916917' -- without CS_ADRESSMANDANT condition
and a.effdt =
(select max(b.effdt)
from ps_cs_adrart_tb b
where b.cs_adressmandant = a.cs_adressmandant and
b.cs_person_id = a.cs_person_id
and b.cs_beziehung_id = a.cs_beziehung_id
and b.seqnum = a.seqnum
and b.effdt <= trunc(sysdate)
)
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 89 | 2 | 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED | PS_CS_ADRART_TB | 1 | 89 | 1 | 00:00:01 |
| * 2 | INDEX RANGE SCAN | PS1CS_ADRART_TB | 1 | | 1 | 00:00:01 |
| 3 | SORT AGGREGATE | | 1 | 59 | | |
| 4 | FIRST ROW | | 1 | 59 | 1 | 00:00:01 |
| * 5 | INDEX RANGE SCAN (MIN/MAX) | PS_CS_ADRART_TB | 1 | 59 | 1 | 00:00:01 |
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 2 - access("A"."CS_KDNR_AS400"='53916917')
* 2 - filter("A"."EFFDT"= (SELECT MAX("B"."EFFDT") FROM "PS_CS_ADRART_TB" "B" WHERE "B"."EFFDT"<=TRUNC(SYSDATE@!) AND "B"."SEQNUM"=:B1 AND "B"."CS_BEZIEHUNG_ID"=:B2 AND "B"."CS_PERSON_ID"=:B3 AND
"B"."CS_ADRESSMANDANT"=:B4))
* 5 - access("B"."CS_ADRESSMANDANT"=:B1 AND "B"."CS_PERSON_ID"=:B2 AND "B"."CS_BEZIEHUNG_ID"=:B3 AND "B"."SEQNUM"=:B4 AND "B"."EFFDT"<=TRUNC(SYSDATE@!))
Note
-----
- dynamic sampling used for this statement
在子选择中使用min()
而不是max()
SELECT A.CS_ADRESS_ID, A.SEQNUM, TO_CHAR(A.EFFDT, 'YYYY-MM-DD'), a.cs_person_id
from PS_CS_ADRART_TB A
where a.cs_kdnr_as400 = '63916917' and a.cs_adressmandant = '001'
and a.effdt =
(select min(b.effdt)
from ps_cs_adrart_tb b
where b.cs_adressmandant = a.cs_adressmandant
and b.cs_person_id = a.cs_person_id
and b.cs_beziehung_id = a.cs_beziehung_id
and b.seqnum = a.seqnum
and b.effdt <= sysdate
);
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 109 | 2 | 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 109 | 2 | 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED | PS_CS_ADRART_TB | 1 | 89 | 1 | 00:00:01 |
| * 3 | INDEX RANGE SCAN | PS1CS_ADRART_TB | 1 | | 1 | 00:00:01 |
| * 4 | VIEW PUSHED PREDICATE | VW_SQ_1 | 1 | 20 | 1 | 00:00:01 |
| 5 | SORT GROUP BY | | 1 | 59 | 1 | 00:00:01 |
| * 6 | INDEX RANGE SCAN | PS_CS_ADRART_TB | 1 | 59 | 1 | 00:00:01 |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 3 - access("A"."CS_KDNR_AS400"='63916917' AND "A"."CS_ADRESSMANDANT"='001')
* 4 - filter("A"."EFFDT"="MIN(B.EFFDT)" AND "ITEM_1"="A"."CS_ADRESSMANDANT")
* 6 - access("B"."CS_ADRESSMANDANT"='001' AND "B"."CS_PERSON_ID"="A"."CS_PERSON_ID" AND "B"."CS_BEZIEHUNG_ID"="A"."CS_BEZIEHUNG_ID" AND "B"."SEQNUM"="A"."SEQNUM" AND "B"."EFFDT"<=SYSDATE@!)
Note
-----
- dynamic sampling used for this statement
重建索引(如上所述)并更新统计数据
EXEC DBMS_STATS.delete_table_stats('SYSADM', 'PS_CS_ADRART_TB');
-- EXEC DBMS_STATS.gather_table_stats('SYSADM', 'PS_CS_ADRART_TB');
EXEC DBMS_STATS.gather_table_stats(ownname => 'SYSADM', tabname => 'PS_CS_ADRART_TB', cascade => true );
没用。
这里发生了什么?任何建议都将受到高度赞赏。提前致谢。
【问题讨论】:
请让我知道你为什么不投这个问题。 问题很清楚,标签很好,包含构建所需结构的所有语句,提供有关表大小的信息,包含许多尝试和每个语句的计划......我可以'不知道这种问题还应该包含什么... +1 12c 计划中的“用于此语句的动态采样 似乎表明尽管有gather_table_stats()
,但统计信息不是最新的。如果你打电话给gather_table_stats()
和cascade => true
?(为了排除显而易见的问题:SYSADM 真的是这些表的所有者吗?)
@a_horse_with_no_name 我们使用选项cascade => true
调用DBMS_STATS.gather_table_stats
,结果相同。是的,SYSADM
是该表的所有者。该数据库是我们的 Oracle 11g 数据库的升级副本。
两个数据库中参数OPTIMIZER_DYNAMIC_SAMPLING的值是多少?
【参考方案1】:
我认为您应该将参数_unnest_subquery
设置为FALSE
。
你可以试试
alter session set "_unnest_subquery" = FALSE;
【讨论】:
就是这样! Oracle 支持建议将隐藏参数 _unnest_subquery 设置为 FALSE 以提高 Oracle 数据库上 PeopleSoft 应用程序的性能。这与bobbydurrettdba.com/2012/11/07/… 中描述的行为相同【参考方案2】:弗兰克。据我了解,您希望通过任何 cs_person_id 和 cs_beziehung_id 获得最后一行,其中 b.effdt 低于或等于今天。首先,您可以收集有关选择性信息的统计数据。它帮助 CBO 做出选择。
BEGIN
dbms_stats.Gather_table_stats('SYSADM', 'PS_CS_ADRART_TB',
method_opt => 'FOR COLUMNS SIZE 254 CS_KDNR_AS400 CS_ADRESSMANDANT');
END;
/
另一个人认为我发现了什么。您有很多索引,可能会被创建为唯一的。它已经包含唯一索引列。
create unique index PS1CS_ADRART_TB on PS_CS_ADRART_TB (CS_KDNR_AS400, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT) ;
create unique index PS0CS_ADRART_TB on PS_CS_ADRART_TB (CS_ADRESS_ID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS1CS_ADRART_TB on PS_CS_ADRART_TB (CS_KDNR_AS400, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS2CS_ADRART_TB on PS_CS_ADRART_TB (ROW_ADDED_DTTM, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS3CS_ADRART_TB on PS_CS_ADRART_TB (ROW_ADDED_OPRID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS4CS_ADRART_TB on PS_CS_ADRART_TB (ROW_LASTMANT_DTTM, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS5CS_ADRART_TB on PS_CS_ADRART_TB (ROW_LASTMANT_OPRID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS6CS_ADRART_TB on PS_CS_ADRART_TB (CS_RECSTAT_XL, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
最后我尝试重写查询以排除子查询
SELECT cs_adress_id,seqnum, effdt_chr
FROM (SELECT /* */ a.cs_adress_id
,a.seqnum
,to_char(a.effdt, 'YYYY-MM-DD') AS effdt_chr
,dense_rank() over(PARTITION BY a.cs_adressmandant, a.cs_person_id
, a.cs_beziehung_id, a.seqnum
ORDER BY a.effdt DESC) AS rnk
FROM ps_cs_adrart_tb a
WHERE a.cs_adressmandant = '001'
AND a.cs_kdnr_as400 = '63916917'
AND a.effdt <= trunc(sysdate)
)
WHERE rnk = 1;
【讨论】:
是的,该语句选择给定人员的当前数据。解释计划中的统计值是最新的(CS_ADRESSMANDANT=001 和 EFFDT=sysdate 的 12746381 行是一个足够的值),因此问题是为什么不使用该索引的其他字段 CS_PERSON_ID、CS_BEZIEHUNG_ID 和 SEQNUM。是的,我们可以更改 select 语句或索引,但我们会尽量减少对应用程序的更改,以避免工作量和副作用带来的风险。 尽管如此,我已经用method_opt => 'FOR COLUMNS SIZE 254 CS_KDNR_AS400 CS_ADRESSMANDANT'
尝试过 - 但没有成功。
它看起来像 hash_group_by 的问题。能否请您检查带有提示/*+ opt_param('_optimizer_aggr_groupby_elim', 'true')*/
的计划查询?
这个提示完全不影响执行计划。以上是关于Oracle 12c 中的子选择性能不佳的主要内容,如果未能解决你的问题,请参考以下文章
Oracle 12c - 插入到选择查询中的不明确列,ORA-00918