1

我在 DB(Oracle 版本 10g)中有一个有趣的问题。

有 2 个实例INS1INS2. 架构中有一个包含 2 个过程的包,SC1它是从架构中调用的,并且在实例和实例SC2上都有一些参数。INS1INS2

第一个过程从使用GTT1创建的全局临时表 中删除on commit preserve rows,然后插入到其中。

proc1(parameter_in) as
begin
    delete from gtt1;

    insert into gtt1
    from <other_table_1>
    where parameter=parameter_in;
end;

proc2(parameter_in) as
begin
    insert into <table_1>
    select * from gtt1, <other_table_1>
    where <join 2 tables>
    and parameter=parameter_in;

    -- again insert in same table with records not in <other_table_1>
    -- as they were deleted during refresh.

    insert into <table_1>
    select * from gtt1
    where not exists
        (row from <other_table_1>);

    delete from gtt1;            
end;

当这些从模式中SC2一起调用时:

begin
    sc1.pkg1.proc1(parameter_in);
    sc1.pkg1.proc2(parameter_in);
end;
/

INS1它在 10 秒内执行。INS2它需要3分钟。

当我单独运行这些程序时,INS2它会在 5 秒内运行:

begin
    sc1.pkg1.proc1(parameter_in);
end;
/

begin
    sc1.pkg1.proc2(parameter_in);
end;
/

我检查了<other_table_1>两个实例的统计数据和表中的数据相同。

有什么建议为什么要花时间在实例上INS2

4

1 回答 1

3

我的猜测是,有人只收集了一张临时表的统计信息,这导致只有一个系统上的计划不好。

统计数据可能对临时表有害——因为数据非常不稳定,很难创建有代表性的统计数据。这就是为什么 Oracle 通常不会在临时表上收集统计信息,而是使用动态采样在运行时估计统计信息。例如,DBMS_STATS.GATHER_SCHEMA_STATS不会收集临时表的统计信息。但是DBMS_STATS.GATHER_TABLE_STATS会。

如果没有统计信息,LAST_ANALYZED 时间戳将为空:

select table_name, last_analyzed from dba_tables where table_name = 'GTT1';

如果是这种情况,请删除统计信息并锁定表,以免再次发生这种情况。

begin
    dbms_stats.delete_table_stats(user, 'GTT1');
    dbms_stats.lock_table_stats(user, 'GTT1');
end;
/

现在,如果有人试图收集统计信息,他们将收到一条错误消息:

begin
    dbms_stats.gather_table_stats(user, 'GTT1');
end;
/

ORA-20005: object statistics are locked (stattype = ALL)
ORA-06512: at "SYS.DBMS_STATS", line 23829
ORA-06512: at "SYS.DBMS_STATS", line 23880
ORA-06512: at line 2

如果您的系统依赖于动态采样,您需要确保参数已启用并合理设置。默认值 2 通常足够好:

select value from v$parameter where name = 'optimizer_dynamic_sampling'

这是一个脚本,展示了如何明确地收集统计信息会导致错误的基数估计。

创建表,用 100K 行填充它们。仅收集其中一张表的统计信息:

create global temporary table test_with_stats3(a number) on commit preserve rows;
create global temporary table test_no_stats3(a number) on commit preserve rows;

insert into test_with_stats3 select level from dual connect by level <= 100000;
insert into test_no_stats3 select level from dual connect by level <= 100000;
commit;

begin
    dbms_stats.gather_schema_stats(user);
end;
/

begin
    dbms_stats.gather_table_stats(user, 'TEST_WITH_STATS3');
end;
/

删除所有行。对于有统计信息的表,Oracle 仍然认为它有 100K 行:

delete from test_with_stats3;
delete from test_no_stats3;
commit;

explain plan for select * from test_with_stats3;
select * from table(dbms_xplan.display);

Plan hash value: 467959123

--------------------------------------------------------------------------------------
| Id  | Operation         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                  |   100K|   488K|    44   (3)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| TEST_WITH_STATS3 |   100K|   488K|    44   (3)| 00:00:01 |
--------------------------------------------------------------------------------------

没有统计信息的表有更准确的估计:

explain plan for select * from test_no_stats3;
select * from table(dbms_xplan.display);

Plan hash value: 2315614086

------------------------------------------------------------------------------------
| Id  | Operation         | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                |     1 |    13 |    43   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| TEST_NO_STATS3 |     1 |    13 |    43   (0)| 00:00:01 |
------------------------------------------------------------------------------------

Note
-----
   - dynamic sampling used for this statement (level=2)
于 2013-06-28T05:16:52.867 回答