Oracle 11g分区表中的并发统计信息收集

时间:2015-03-04 15:54:20

标签: oracle oracle11g etl data-warehouse table-statistics

我正在开发Oracle 11g上的DWH。我们有一些大表(2.5亿行),按值划分。每个分区都分配给不同的馈送源,每个分区独立于其他分区,因此可以同时加载和处理它们。

数据分布非常不均匀,我们有数百万行的分区,并且分区不超过一百行,但我没有选择分区方案,顺便说一下我无法改变它。

考虑到数据量,我们必须确保每个分区始终具有最新的统计信息,因为如果后续的详细说明没有对数据的最佳访问权限,那么它们将永远存在。

因此,对于每个并发的ETL线程,我们

  1. 截断分区
  2. 使用
  3. 从暂存区域加载数据

    SELECT /*+ APPEND */ INTO big_table PARTITION(part1) FROM temp_table WHERE partition_colum = PART1

    (这样我们就有了直接的路径而且我们没有锁定整个表格)

    1. 我们收集修改后的分区的统计信息。
    2. 在项目的第一阶段,我们使用APPROX_GLOBAL_AND_PARTITION策略并像魅力一样工作

       dbms_stats.gather_table_stats(ownname=>myschema,
                                    tabname=>big_table,
                                    partname=>part1,
                                    estimate_percent=>1,
                                    granularity=>'APPROX_GLOBAL_AND_PARTITION',
                                    CASCADE=>dbms_stats.auto_cascade,
                                    degree=>dbms_stats.auto_degree) 
      

      但是,我们的缺点是,当我们加载一个小分区时,APPROX_GLOBAL部分占主导地位(仍然比GLOBAL快很多),而对于一个小分区,我们有例如10秒加载,20分钟统计数据。

      因此我们建议切换到11g的 INCREMENTAL STATS 功能,这意味着您没有指定已修改的分区,您将所有参数保留为auto,而Oracle会将其保留为魔术,自动了解触摸了哪个分区。它确实有效,我们已经加快了小分区的速度。打开该功能后,呼叫变为

       dbms_stats.gather_table_stats(ownname=>myschema,
                                    tabname=>big_table,
                                    estimate_percent=>dbms_stats.auto_sample_size,
                                    granularity=>'AUTO',
                                    CASCADE=>dbms_stats.auto_cascade,
                                    degree=>dbms_stats.auto_degree) 
      

      请注意,您不再通过分区,并且未指定样本百分比。

      但是,我们有一个缺点,可能比前一个更糟糕,这与我们的高水平并行性有关。

      假设我们有两个同时启动的大分区,它们几乎会同时完成加载阶段。

      1. 第一个线程结束插入语句,提交并启动统计信息收集。统计程序通知有2个分区已修改(这是正确的,一个已满,第二个被截断,正在进行事务),正确更新了两个分区的统计信息。

      2. 最终第二个分区结束,收集统计信息,它看到所有分区已经更新,什么都不做(这是不正确的,因为第二个线程同时提交了数据)。

      3. 结果是:

        PARTITION NAME | LAST ANALYZED        | NUM ROWS | BLOCKS | SAMPLE SIZE
        -----------------------------------------------------------------------
        PART1          | 04-MAR-2015 15:40:42 | 805731   | 20314  | 805731
        PART2          | 04-MAR-2015 15:41:48 | 0        | 16234  | (null)
        

        结果是我偶尔会遇到非最佳计划(这意味着杀死会话,手动刷新统计数据,再次手动启动进程)。

        我甚至试图在收集上放置一个独占锁,所以只有一个线程可以同时在同一个表上收集统计信息,但没有任何改变。

        恕我直言,这是一种奇怪的行为,因为统计程序第二次被调用时,应检查第二个分区上的最后一次提交,并且应该看到它比上次统计数据收集时间更新。但似乎没有发生。

        我做错了吗?这是Oracle的错误吗?如何保证所有统计信息始终是最新的,并且启用了增量统计功能,并且具有高级别的并发性?

6 个答案:

答案 0 :(得分:2)

我设法通过此功能达成了妥协。

PROCEDURE gather_tb_partiz(
    p_tblname IN VARCHAR2,
    p_partname IN VARCHAR2)
IS
  v_stale all_tab_statistics.stale_stats%TYPE;
BEGIN
  BEGIN
    SELECT stale_stats
    INTO v_stale
    FROM user_tab_statistics
    WHERE table_name = p_tblname
    AND object_type = 'TABLE';
  EXCEPTION
  WHEN NO_DATA_FOUND THEN
    v_stale := 'YES';
  END;
  IF v_stale = 'YES' THEN
    dbms_stats.gather_table_stats(ownname=>myschema, 
                                  tabname=> p_tblname,
                                  partname=>p_partname,
                                  degree=>dbms_stats.auto_degree,
                                  granularity=>'APPROX_GLOBAL AND PARTITION') ;
  ELSE
    dbms_stats.gather_table_stats(ownname=>myschema,
                                 tabname=>p_tblname,
                                 partname=>p_partname,
                                 degree=>dbms_stats.auto_degree,
                                 granularity=>'PARTITION') ;
  END IF;
END gather_tb_partiz;

在每个ETL结束时,如果添加/删除/修改的行数足够低而不将表标记为过时(默认情况下为10%,可以使用STALE_PERCENT参数调整),我只收集分区统计信息;否则我收集全局和分区统计信息。

这使得小分区的ETL保持快速,因为没有必须恢复全局分区,并且大分区是安全的,因为任何后续查询都将具有新的统计信息并且可能使用最佳计划。

无论如何都要启用增量统计,因此每当必须重新计算全局时,它都非常快,因为聚合分区级别统计信息并且不执行完整扫描。

我不确定是否在启用增量的情况下" APPROX_GLOBAL AND PARTITION"和"全球和分区"在某些方面有所不同,因为增量和近似基本相同:聚合统计数据和直方图而不进行全面扫描。

答案 1 :(得分:1)

您是否尝试过增量统计信息,但仍明确指定要分析的分区?

 dbms_stats.gather_table_stats(ownname=>myschema,
                              tabname=>big_table,
                              partname=>part,
                              degree=>dbms_stats.auto_degree);

答案 2 :(得分:1)

对于您的表,陈旧(昨天)的全局统计数据不如完全无效的分区统计数据(0行)有害。我可以提出两种我们使用的替代方法:

  • 在加载所有分区后立即由ETL工具执行单独的GLOBAL统计信息收集。如果花费太长时间,请使用estimate_percent,因为dbms_stats.auto_degree可能会超过1%
  • 将所有数据加载到DW后,在白天晚些时候运行的单独数据库作业中收集全局(以及所有其他陈旧)统计信息。

关键在于,与新鲜度略有不同的陈旧统计数据几乎同样出色。如果统计信息显示0行,它们将终止任何查询。

答案 3 :(得分:1)

考虑到您要实现的目标,您需要在所有分区的特定时间间隔内运行统计信息,而不是在加载每个分区的过程结束时运行。如果这是一个实时表并且有不间断的数据加载可能会很有挑战性,但由于这些是大型DW表,我真的怀疑是这样的。因此,最好的办法是在加载所有分区时收集统计信息,这将确保收集数据发生变化或缺少统计信息的分区的统计信息,并根据分区级别统计信息和概要更新全局统计信息。

但是,要执行此操作,您需要打开表格的增量功能(11gR1)。

EXEC DBMS_STATS.SET_TABLE_PREFS('<Owner>','BIG_TABLE','INCREMENTAL','TRUE');

在每次加载结束时,使用GATHER_TABLE_STATS命令收集表统计信息。您不需要指定分区名称。另外,请勿指定粒度参数。

EXEC DBMS_STATS.GATHER_TABLE_STATS('<Owner>','BIG_TABLE');

答案 4 :(得分:0)

请检查您是否使用DBMS_STATS设置表首选项以收集增量统计信息。This oracle blog说明在每行受影响后将收集统计信息。

  

增量统计信息维护需要收集有关将更改全局或表级统计信息的任何分区的统计信息。例如,在表

中插入或更新一行后,列的最小值或最大值可能会更改
BEGIN 
DBMS_STATS.SET_TABLE_PREFS(myschema,'BIG_TABLE','INCREMENTAL','TRUE'); 
END;

答案 5 :(得分:-1)

我对它有点生疏,所以首先提出一个问题: 你尝试序列化分区加载?如果是这样,统计数据运行的时间和程度如何?请注意,由于加载时间比统计信息收集小得多,我想这也可以作为临时解决方法。

追加提示会影响重做大小,这意味着事务只会跟踪某些内容,因此统计信息可能无法计算新数据: http://oracle-base.com/articles/misc/append-hint.php

大声思考:由于直接路径插入确实在分区末尾追加行并最终在最后更新元数据,因此已经运行的线程收集统计信息可能已读取未更新(陈旧)数据。因此它可能不是一个错误,锁定线程将无法实现任何目标。

例如,您可以测试此行为暂时将您的表/分区切换到LOGGING,并查看它是如何工作的(当然,速度较慢,但​​这是一个测试)。你能做到吗?

编辑:增量统计数据无论如何都应该有效,甚至禁用并行统计信息收集,因为无论收集的方式如何,它都会依赖于增量值: https://blogs.oracle.com/optimizer/entry/incremental_statistics_maintenance_what_statistics