收集分区表上的统计信息

时间:2017-01-01 12:24:44

标签: oracle oracle11g

我每天都有表 ABC 间隔分区。每个分区用于特定日期的查询。即使我每天安排工作@nyt来收集统计数据,然后在统计数据收集之前使用该表的查询也不会使用最佳计划。

3 个答案:

答案 0 :(得分:0)

收集分区表上的优化程序统计信息并不是一项简单的任务,但有一些注意事项。 特别是在每日分区架构上,它可能不是每天收集一次分区统计信息的最佳解决方案。

为了演示它,假设我们没有每日模式,而是每年的事务数据分区。问题是, 可以在1月1日(或1月6日或12月31日)收集统计数据吗? 答案是明确否,因为在第一种情况下,分区将被视为(几乎)为空,在后一种情况下 统计数据是现实的,但收集的时间太晚了。

考虑到这一点,IMO有三种可能的方法来处理它

1)根本不收集统计数据(并使用动态抽样)

2)重复收集分区统计信息(比方说每小时)

3)不收集统计信息,但设置它们,查询执行正常

最佳选择取决于您的数据和访问模式,因此我只考虑实施这些选项的一些细节。

示例数据

让我们生成一个包含一个完整和一个几乎为空的每日分区的表。

该表在GROUP_ID列上有一个本地索引。这个练习的目的是获得FULL TABLE SCAN 在访问大分区时访问小分区和INDEX ACCESS

CREATE TABLE  mytab
   (    id number not null,
      group_id number,
      trans_date date,
      pad varchar2(4000))
PARTITION BY RANGE (trans_date)
INTERVAL (NUMTODSINTERVAL(1,'DAY'))
(
   PARTITION part_01 values LESS THAN (TO_DATE('31-12-2016','DD-MM-YYYY'))
);

create index mytab_idx1 on mytab(id) local;
create index mytab_idx2 on mytab(group_id) local;

-- full day partition
insert into mytab (id, group_id, trans_date, pad)
select rownum id, trunc(rownum/1000) group_id, to_date('31122016','ddmmyyyy'), lpad('x',3000,'x') from dual 
connect by  level <= 100000;
commit;

-- nearly empty day partition
insert into mytab (id, group_id, trans_date, pad)
select rownum id, trunc(rownum/1000) group_id, to_date('01012017','ddmmyyyy'), lpad('x',3000,'x') from dual
connect by  level <= 1000; 
commit;

动态采样

如果目标对象根本没有统计信息,Oracle会执行动态采样(又名dynamic statistics) 通过一点开销,Oracle在解析语句时会计算统计信息。所以它不会陈旧。

访问几乎空的分区Oracle正确选择FULL TABLE SCAN

EXPLAIN PLAN  SET STATEMENT_ID = 'jara1' into   plan_table  FOR
select   * from mytab 
where trans_date = TO_DATE('01-01-2017','DD-MM-YYYY') and group_id = 0;

SELECT * FROM table(DBMS_XPLAN.DISPLAY('plan_table', 'jara1','ALL')); 

Plan hash value: 4018216072

------------------------------------------------------------------------------------------------
| Id  | Operation              | Name  | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |       |   958 |  1905K|   274   (0)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE SINGLE|       |   958 |  1905K|   274   (0)| 00:00:01 |     3 |     3 |
|*  2 |   TABLE ACCESS FULL    | MYTAB |   958 |  1905K|   274   (0)| 00:00:01 |     3 |     3 |
------------------------------------------------------------------------------------------------

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

   2 - filter("TRANS_DATE"=TO_DATE(' 2017-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') 
              AND "GROUP_ID"=0)

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

...使用

时访问完整分区INDEX ACCESS
EXPLAIN PLAN  SET STATEMENT_ID = 'jara1' into   plan_table  FOR
select * from mytab 
where trans_date = TO_DATE('31-12-2016','DD-MM-YYYY') and group_id = 0;

SELECT * FROM table(DBMS_XPLAN.DISPLAY('plan_table', 'jara1','ALL'));

Plan hash value: 984912596

-------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                  | Name       | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                           |            |  1608 |  3198K|  9021   (1)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE SINGLE                    |            |  1608 |  3198K|  9021   (1)| 00:00:01 |     2 |     2 |
|*  2 |   TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| MYTAB      |  1608 |  3198K|  9021   (1)| 00:00:01 |     2 |     2 |
|*  3 |    INDEX RANGE SCAN                        | MYTAB_IDX2 |  1608 |       |  2880   (1)| 00:00:01 |     2 |     2 |
-------------------------------------------------------------------------------------------------------------------------

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

   2 - filter("TRANS_DATE"=TO_DATE(' 2016-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
   3 - access("GROUP_ID"=0)

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

所以我们看到动态采样工作正常,选择正确的访问方法。

经常收集分区统计信息

重复收集工作可以缓解问题,即分区不断增长。

期限取决于交易率。

仅收集一个分区的统计信息的示例

 exec dbms_stats.gather_table_stats(OWNNAME=>user,TABNAME=>'MYTAB', PARTNAME=>'SYS_P10030',   CASCADE=> TRUE); 

必须避免的最坏情况是*表明分区为空的统计信息,但(同时)分区是大量填充的。

设置统计

这种方法假定&#34;正确&#34;已知查询的访问路径。在我们的例子中 我们可以访问一个带有FULL TABLE SCAN的几乎空的分区,但索引访问对于这样的分区是好的 同样。所以我们可以设置分区统计数据,这样就可以完成INDEX ACCESS。

一种可能的(非常简单的)架构是复制前一天的统计数据。

此调用将统计信息从分区SYS_P10029复制到分区SYS_P10030

 exec DBMS_STATS.COPY_TABLE_STATS (OWNNAME=>user,TABNAME=>'MYTAB',srcpartname=>'SYS_P10029',dstpartname=> 'SYS_P10030');    

换句话说,在创建分区后立即启动统计信息,就像完整填充的分区一样。

答案 1 :(得分:0)

在我的应用程序中,我每天通过调度程序作业运行此过程一次。它收集最近分区的统计信息。

PROCEDURE GatherIndexStats IS

    CURSOR IndexPartition(indName IN VARCHAR2) IS
    SELECT INDEX_NAME, PARTITION_NAME
    FROM USER_IND_STATISTICS i
        JOIN USER_TAB_PARTITIONS t USING (TABLE_NAME, PARTITION_NAME)
    WHERE TABLE_NAME = 'ABC'
        AND i.LAST_ANALYZED IS NULL
        AND OBJECT_TYPE = 'PARTITION'
        AND INDEX_NAME = indName
    ORDER BY INDEX_NAME, PARTITION_NAME DESC
    OFFSET 1 ROW FETCH FIRST 2 ROW ONLY;

BEGIN

    FOR aIndex IN (SELECT INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME = 'ABC') LOOP
        FOR aInd IN IndexPartition(aIndex.INDEX_NAME) LOOP
            DBMS_STATS.GATHER_INDEX_STATS(USER, aInd.INDEX_NAME, aInd.PARTITION_NAME);
        END LOOP;
    END LOOP;   

END GatherIndexStats;

在我的应用程序中,我只需要获取索引统计信息而不是全表统计信息。如果您想获得索引和表统计信息,请使用以下过程:

PROCEDURE GatherTableStats IS

    CURSOR TablePartition IS
    SELECT INDEX_NAME, PARTITION_NAME
    FROM USER_TAB_STATISTICS i
        JOIN USER_TAB_PARTITIONS t USING (TABLE_NAME, PARTITION_NAME)
    WHERE TABLE_NAME = 'ABC'
        AND i.LAST_ANALYZED IS NULL
        AND OBJECT_TYPE = 'PARTITION'
    ORDER BY PARTITION_NAME DESC
    OFFSET 1 ROW FETCH FIRST 2 ROW ONLY;

BEGIN

    FOR aPart IN TablePartition LOOP
        DBMS_STATS.GATHER_TABLE_STATS(USER, 'ABC', aPart.PARTITION_NAME);
    END LOOP;   

END GatherTableStats;

答案 2 :(得分:0)

统计数据应作为显着更改数据的任何流程的一部分进行收集。不要依赖夜间工作来收集统计数据,尤其是在大型数据仓库中。

仅在夜间工作中收集统计数据有许多潜在的缺点:

  1. 处理具有奇怪的时间依赖性。统计窗口可能很难协调。有时如果工作太多,你关心的桌子可能没有时间进行分析。
  2. 有几种类型的统计作业(调度程序作业,DBA_JOBS,auto_tasks),所有这些作业都会被禁用的程度超过预期。
  3. 在错误的时间收集统计数据要差得多而不是根本没有统计数据。如果没有统计数据,那么Oracle可以使用动态抽样来完成一项体面的工作。但是,如果夜间工作恰好在表空的短暂时期内收集统计数据,那么统计数据可能会出现严重错误并且性能会受到影响。我已经多次看到过这种情况;这些错误往往归咎于环境差异&#34;但是如果你把关键步骤放到偶然,那么环境就会随机失败。
  4. 将统计信息作为数据加载过程的一部分进行收集有许多潜在的优势。由于您比某些通用夜间统计工作更了解流程和表格,因此您可以利用许多高级功能:

    1. 如果系统在数据加载后不忙,则可以使用并行度和DEGREE =&gt; 8这样的参数。
    2. 如果它是12c中的直接路径写入,您可以在使用GATHER_OPTIMIZER_STATISTICS提示加载数据时自动收集统计数据。
    3. 如果是区间分区表,您可能需要设置增量统计信息收集。这使得进程只花时间收集分区的统计信息,并且全局统计信息可以免费更新。
    4. 如果进程已禁用并重建索引,则可以避免使用参数NOCASCADE =&gt; TRUE重新收集索引统计信息。
    5. 不要将统计数据外包给其他预定的工作。统计数据非常重要和棘手,它们应该与任何正在进行重大数据更改的程序完全集成。