#oracle #oracle11g #oracle12c
#Oracle #oracle11g #oracle12c
Вопрос:
После перехода с 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
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
As you can see, from the index PS_CS_ADRART_TB
only CS_ADRESSMANDANT
and EFFDT
are used to filter the data and that is quite bad.
With the following slightly different select statements the index is used by Oracle 12c as expected to determine data of subselect.
Changing where condition of outer select
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. Пожалуйста, дайте мне знать, почему вы не проголосовали за вопрос.
2. Вопрос ясен, хорошо помечен, содержит все инструкции для построения необходимых структур, предоставляет информацию о размере таблиц, содержит много попыток и план для каждого оператора… Я не могу понять, что еще должен содержать этот вопрос … 1
3. » Динамическая выборка, используемая для этого утверждения в плане 12c, по-видимому, указывает на то, что статистика не является актуальной, несмотря на
gather_table_stats()
. Изменится ли это, если вы вызоветеgather_table_stats()
сcascade => true
помощью? (И чтобы исключить очевидное: действительно ли СИСТЕМНЫЙ администратор является владельцем этих таблиц?)4. @a_horse_with_no_name Мы вызвали
DBMS_STATS.gather_table_stats
с опциейcascade => true
с тем же результатом. И да,SYSADM
является владельцем таблицы. База данных является обновленной копией нашей базы данных Oracle 11g.5. Каково значение параметра OPTIMIZER_DYNAMIC_SAMPLING в обеих базах данных?
Ответ №1:
Я думаю, вам следует установить параметр _unnest_subquery
FALSE
равным .
Вы можете попробовать это с
alter session set "_unnest_subquery" = FALSE;
Комментарии:
1. Вот и все! Служба поддержки Oracle рекомендует установить для скрытого параметра _unnest_subquery значение FALSE для повышения производительности приложений PeopleSoft в базах данных Oracle. Это то же поведение, что описано в 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;
Комментарии:
1. Да, оператор выбирает текущие данные для данного пользователя. Значения статистики в плане объяснения обновлены (12746381 строк для CS_ADRESSMANDANT=001 и EFFDT=sysdate является адекватным значением), поэтому возникает вопрос, почему дополнительные поля CS_PERSON_ID, CS_BEZIEHUNG_ID и SEQNUM этого индекса не используются. И да, мы могли бы изменить оператор select или индексы, но мы стараемся минимизировать изменения в нашем приложении, чтобы избежать усилий и риска побочных эффектов.
2. Тем не менее, я пробовал это с
method_opt => 'FOR COLUMNS SIZE 254 CS_KDNR_AS400 CS_ADRESSMANDANT'
помощью — но безуспешно.3. Это похоже на проблему с hash_group_by. Не могли бы вы, пожалуйста, проверить запрос плана с помощью подсказки
/* opt_param('_optimizer_aggr_groupby_elim', 'true')*/
?4. Эта подсказка вообще не влияет на план выполнения.