Oracle 11g partiitioned table 上的并发统计信息收集

concurrent statistics gathering on Oracle 11g partiitioned table

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

数据分布很不均匀,我们有百万行的分区,也有不超过一百行的分区,但是我没有选择分区方案,顺便说一下我不能改变它。

考虑到数据量,我们必须保证每个分区都有最新的统计信息,因为如果后续的阐述没有对数据的最佳访问,它们将永远存在。

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

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

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

(这样我们就有了直接路径而且我们不会锁定整个table)

  1. 我们收集修改分区的统计信息。

在项目的第一阶段,我们使用了 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. 最终第二个分区结束,收集统计信息,它看到所有分区都已经更新,什么都不做(这是不正确的,因为第二个线程同时提交了数据)。

结果是:

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)

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

我什至尝试在收集上加一个独占锁,这样一次最多只能有一个线程收集同一个 table 上的统计数据,但没有任何改变。

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

我做错了什么吗?它是 Oracle 错误吗?我如何保证所有统计信息始终是最新的,并启用增量统计信息功能和高并发性?

我对此有点生疏,所以首先要问一个问题: 您是否尝试序列化分区加载?如果是这样,统计 运行 需要多长时间,效果如何?请注意,由于加载时间比收集统计数据要短得多,我想这也可以作为一种临时解决方法。

附加提示确实影响重做大小,这意味着事务只是跟踪一些东西,因此统计可能不会计算新数据: http://oracle-base.com/articles/misc/append-hint.php

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

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

编辑:增量统计无论如何都应该工作,甚至禁用并行统计收集,因为无论它们是如何收集的,它都会依赖于增量值: https://blogs.oracle.com/optimizer/entry/incremental_statistics_maintenance_what_statistics

您是否尝试过对其进行增量统计,但仍明确命名要分析的分区?

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

对于您的 table,陈旧的(昨天的)全局统计数据没有完全无效的分区统计数据(0 行)那么有害。我可以提出 2 个我们使用的替代方法:

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

关键是陈旧的统计数据与新的仅略有不同,几乎一样好。如果统计信息显示 0 行,它们将终止任何查询。

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

Incremental statistics maintenance needs to gather statistics on any partition that will change the global or table level statistics. For instance, the min or max value for a column could change after just one row is inserted or updated in the table

BEGIN 
DBMS_STATS.SET_TABLE_PREFS(myschema,'BIG_TABLE','INCREMENTAL','TRUE'); 
END;

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

但是,您需要为 table (11gR1) 打开增量功能。

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

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

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

我设法与此功能达成妥协。

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 结束时,如果 added/deleted/modified 行数足够低,不会将 table 标记为陈旧(默认为 10%,可以使用 STALE_PERCENT 参数),我只收集分区统计信息;否则我收集全局和分区统计信息。

这可以使小分区的 ETL 保持快速,因为不必重新收集全局分区,并且大分区是安全的,因为任何后续查询都会有新的统计信息,并且很可能会使用最佳计划。

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

我不确定启用增量后,"APPROX_GLOBAL AND PARTITION" 和 "GLOBAL AND PARTITION" 是否在某些方面有所不同,因为增量和近似都做基本相同的事情:聚合统计数据和直方图而不做完整的扫描.