【问题标题】:Gather stats on partition table收集分区表的统计信息
【发布时间】:2017-05-15 21:17:40
【问题描述】:

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

【问题讨论】:

  • 添加查询和ddl的计划
  • 我想知道每当添加新分区并且有大量数据加载时,我们是否自动收集了统计信息。如果没有,那么我们如何收集统计数据
  • 查看包DBMS_STATS.GATHER_TABLE_STATISTICS,也可以指定分区。

标签: oracle oracle11g


【解决方案1】:

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

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】:

    收集分区表上的优化器统计数据并非易事,但需要注意一些事项。 特别是在每日分区模式上,它可能不是每天收集一次分区统计信息的最佳解决方案。

    为了证明这一点,假设我们没有每日模式,而是每年的交易数据分区。问题是, 可以在 1 月 1 日(或 1 月 1 日或 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); 
    

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

    设置统计数据

    这种方法假定查询的“正确”访问路径是已知的。在我们的例子中 我们可以使用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');    
    

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

    【讨论】:

      【解决方案3】:

      应在任何显着更改数据的过程中手动收集统计信息。不要仅仅依靠夜间作业来收集统计数据,尤其是在大型数据仓库中。

      仅使用夜间作业进行统计收集的问题

      1. 您的流程具有不可预测的时间依赖性。统计窗口可能难以协调,并且您不知道何时分析您的表。如果工作量太大,或者有人愚蠢地禁用了计划作业或自动任务,那么您关心的表可能根本不会得到分析。
      2. 在错误的时间收集统计信息比没有统计信息更糟糕。如果没有统计信息,Oracle 可以使用动态抽样来猜测统计信息。但是,如果夜间工作恰好在表为空的短暂时间内收集统计信息,则统计信息可能会出现严重错误,并且性能会受到影响。

      向您的流程添加手动统计收集的优势

      由于您比一些通用的夜间统计工作更了解流程和表格,因此您可以利用许多高级功能:

      1. 并行度如果系统在数据加载后不忙,则可以将并行度与DEGREE=&gt;8 之类的参数一起使用。
      2. 直接路径收集如果是直接路径写入,则在 12c 中,您可以在加载数据时自动收集统计信息,同时使用 GATHER_OPTIMIZER_STATISTICS 提示。
      3. 增量 如果是间隔分区表,您可能需要设置增量统计信息收集。这让该进程只花时间收集分区的统计信息,并且免费更新全局统计信息。
      4. 索引 如果进程禁用并重建索引,它可以避免使用参数NOCASCADE=&gt;TRUE 重新收集索引统计信息。

      不要将所有统计信息收集外包给预定的工作。统计数据是如此重要和棘手,以至于它们应该与任何进行重大数据更改的程序完全集成。

      【讨论】:

        猜你喜欢
        • 2015-05-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-13
        • 2021-07-28
        • 1970-01-01
        相关资源
        最近更新 更多