2008/06/25

Предикат из чек-констрейнта

Еще одна, очевидно, ошибка, связанная со сгенеренными предикатами.


drop table ch1;
create table ch1 (f1 number(1) constraint ch1_ck_f1 check (f1 in ('1','2','3')));
insert into ch1 values (1);
insert into ch1 values (2);
insert into ch1 values (3);
insert into ch1 values (4);
commit;
exec dbms_stats.gather_table_stats(ownname=>null,tabname=>'ch1')
select * from ch1 where f1=1;
explain plan for
select * from ch1 where f1=1;
select * from table(dbms_xplan.display);


Как то так вышло у моего коллеги, что для поля с типом NUMBER был создан чек-констрейнт ...in ('1','2','3').
Оптимизатор сгенерил фактически лишний и не корректный предикат из чек-констрейнта.
В итоге результат таков:


SQL> select * from ch1 where f1=1;

no rows selected

SQL> explain plan for
2 select * from ch1 where f1=1;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3341507894

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 | 0 (0)| |
|* 1 | FILTER | | | | | |
|* 2 | TABLE ACCESS FULL| CH1 | 1 | 3 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
---------------------------------------------------

1 - filter(NULL IS NOT NULL)
2 - filter("F1"=1)


а ежели еще вот так


alter table ch1 disable constraint ch1_ck_f1;
select * from ch1 where f1=1;
explain plan for
select * from ch1 where f1=1;
select * from table(dbms_xplan.display);


то


SQL> alter table ch1 disable constraint ch1_ck_f1;

Table altered.

SQL> select * from ch1 where f1=1;

F1
----------
1

SQL> delete from plan_table;

3 rows deleted.

SQL> explain plan for
2 select * from ch1 where f1=1;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2138608289

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| CH1 | 1 | 3 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("F1"=1)



Проверял на следующих версиях: 9.2.0.4, 10.2.0.1, 10.2.0.4, 11.1.0.6 - проявлялось только на 10.2.0.1.

Tuning by Cardinality Feedback в действии

В свое время, статья Tuning by Cardinality Feedback произвеля на меня неизгладимое впечатление.
И вот теперь случилось применить ее в одном интересном случае. Вот такой план имел место быть на промышленной площадке.
Сразу же бросается в глаза серъезное расхождение между прогнозом оптимизатора и статистикой времени исполнения. Первая стадия анализа привела к фрагменту плана, в котором, как мне кажется, находится ключевая ошибка оптимизатора в оценке кардинальности:

-------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time | A-Rows |
-------------------------------------------------------------------------------------------------------------------------------------------------------
...
|* 17 | HASH JOIN ANTI | | 1 | 1 | 54 | | 7 (15)| 00:00:01 | 18 |
| 18 | NESTED LOOPS | | 1 | 1 | 42 | | 2 (0)| 00:00:01 | 21 |
|* 19 | TABLE ACCESS BY USER ROWID | IKIS_USERS_ATTR | 1 | 1 | 25 | | 1 (0)| 00:00:01 | 1 |
|* 20 | INDEX FULL SCAN | PK_IKIS_ROLE | 1 | 1 | 17 | | 1 (0)| 00:00:01 | 21 |
| 21 | VIEW | VW_NSO_3 | 1 | 12 | 144 | | 4 (0)| 00:00:01 | 3 |
| 22 | NESTED LOOPS | | 1 | 12 | 600 | | 4 (0)| 00:00:01 | 3 |
|* 23 | TABLE ACCESS FULL | IKIS_RESOURCE | 1 | 9 | 162 | | 3 (0)| 00:00:01 | 4 |
|* 24 | INDEX RANGE SCAN | PK_RSRC2ROLE | 4 | 1 | 32 | | 1 (0)| 00:00:01 | 3 |
...


все остальное лавинообразно преумножало ошибку, сделанную на 20 шаге плана.
Интересный предикат был сгенерен для этого 20 шага:


20 - filter(("IKIS_SUBSYS_UTIL"."GETINSTANCEPREF"()||"RS"."IRL_NAME"<>'PUBLIC' AND "IKIS_SUBSYS_UTIL"."GETINSTANCEPREF"()||"RS"."IRL_NAME"<>'_NEXT_USER'))


Собственно, данный предикат сгенерен на основе where clause представления dba_roles, которое участвует в данном запросе.
Далее простой test case, имитирующий данную ситуацию:


drop table r1;
drop table r2;
create table r1 as select rownum id,x.* from all_objects x where rownum<=1000;

update r1 set object_name='SSDR' where mod(id,276)=0;
update r1 set object_name='GSDR' where mod(id,876)=0;

create table r2 as select * from r1 where mod(object_id,2)=0;

update r1 set object_name='XXX'||substr(object_name,1,25);
update r2 set object_name=substr(object_name,1,25);


commit;

exec dbms_stats.gather_table_stats(ownname=>null,tabname=>'r1')
exec dbms_stats.gather_table_stats(ownname=>null,tabname=>'r2')

create or replace view v1
as
select * from r1
where object_name<>'XXXSSDR' and object_name<>'XXXGSDR' and object_type='INDEX';

explain plan for
select count(1)
from v1,r2
where v1.object_name='XXX'||r2.object_name;


и ожидаемый, аналогичный полученному на пром-системе, результат с ошибкой оценки кардинальности для таблицы R2:


----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 40 | 9 (12)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 40 | | |
|* 2 | HASH JOIN | | 1 | 40 | 9 (12)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| R2 | 1 | 15 | 3 (0)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| R1 | 111 | 2775 | 5 (0)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("OBJECT_NAME"='XXX'||"R2"."OBJECT_NAME")
3 - filter('XXX'||"R2"."OBJECT_NAME"<>'XXXSSDR' AND
'XXX'||"R2"."OBJECT_NAME"<>'XXXGSDR')
4 - filter("OBJECT_TYPE"='INDEX' AND "OBJECT_NAME"<>'XXXSSDR' AND
"OBJECT_NAME"<>'XXXGSDR')


Очевидно, что ситуация в следующем: оптимизатор генерит предикат и крайне неверно оценивает его селективность.
Собственно workaround:
1) каким-либо образом заставить оптимизатор не генерить предикат, для этого можно использовать хинт NO_MERGE или NO_QUERY_TRANSFORMATION. В таком случае план будет имеет вид:

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 32 | 9 (12)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 32 | | |
|* 2 | HASH JOIN | | 57 | 1824 | 9 (12)| 00:00:01 |
| 3 | VIEW | V1 | 111 | 1887 | 5 (0)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| R1 | 111 | 2775 | 5 (0)| 00:00:01 |
| 5 | TABLE ACCESS FULL | R2 | 501 | 7515 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("V1"."OBJECT_NAME"='XXX'||"R2"."OBJECT_NAME")
4 - filter("OBJECT_TYPE"='INDEX' AND "OBJECT_NAME"<>'XXXSSDR' AND
"OBJECT_NAME"<>'XXXGSDR')


План исходного запроса с хинтом NO_QUERY_TRANSFORMATION тяжеловат, но по времени исполнения все таки существенно лучше.

2) пока искал другие пути обнаружил, что это явление - ошибка в коде оптимизатора.
Проверял следующие версии 9.2.0.4 SE Linux, 10.2.0.1 SE/EE Linux,
10.2.0.1 SE Windows, 11.1.0.6 EE Windows.
Этот эффект проявился исключиетльно на 10.2.0.1 SE Windows (такая же версия была и в продукционной системе, где собственно проблема и была обнаружена).

2008/06/17

Этот замечательный инструмент SQLTXPLAIN

В ноте 215187.1 среди прочего упоминается замечательный инструмент SQLTXPLAIN - незаменимая штуковина для аналитика.
После нескольких запусков я обнаружил маленькую неприятную деталь при использовании режима XTRACT: ORA-07445: caught exception [ACCESS_VIOLATION] at [_msqsub()+23].
Проверял на версии 10.2.0.1 (Linux 32 bit), а также на 11.1.0.6 (Win32).
Похоже, что ошибка возникает в процессе повторной генерации отчета, после перезапуска анализируемого запроса.

Одним из возможных решений проблемы может стать следующее изменение (благо, инструмент поставляется в открытом виде):
в пакете SQLTXPLAIN.sqlt$d изменить запрос, находящийся ориентировочно на 1710 строке

FOR k IN (SELECT *
FROM sqlt$_gv$sql_plan
WHERE ((hash_value = sql_rec.hash_value AND address = sql_rec.address) or sql_id = sql_rec.sql_id)
AND child_number = j.child_number
ORDER BY id)

на такой вариант:

FOR k IN (SELECT inst_id, address, hash_value, sql_id, plan_hash_value, child_address, child_number, timestamp, operation, options, object_node, object#, object_owner, object_name, object_alias, object_type, optimizer, id, parent_id, depth, position, search_columns, cost, cardinality, bytes, other_tag, partition_start, partition_stop, partition_id, other, distribution, cpu_cost, io_cost, temp_space, access_predicates, filter_predicates, projection, time, qblock_name, remarks, other_xml
FROM sqlt$_gv$sql_plan
WHERE hash_value = sql_rec.hash_value AND address = sql_rec.address
AND child_number = j.child_number
union all
SELECT inst_id, address, hash_value, sql_id, plan_hash_value, child_address, child_number, timestamp, operation, options, object_node, object#, object_owner, object_name, object_alias, object_type, optimizer, id, parent_id, depth, position, search_columns, cost, cardinality, bytes, other_tag, partition_start, partition_stop, partition_id, other, distribution, cpu_cost, io_cost, temp_space, access_predicates, filter_predicates, projection, time, qblock_name, remarks, other_xml
FROM sqlt$_gv$sql_plan
WHERE sql_id = sql_rec.sql_id
AND child_number = j.child_number
ORDER BY id)