14

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

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

考虑到数据量,我们必须保证每个分区总是有最新的统计数据,因为如果后续的细化没有对数据的最佳访问,它们将永远持续下去。

所以对于每个并发的 ETL 线程,我们

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

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

(这样我们有直接的路径,我们不锁定整个表)

  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) 

请注意,您不再通过分区,也没有指定样本百分比。

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

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

  1. 第一个线程结束插入语句、提交并启动统计信息收集。stats 程序注意到有 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)

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

我什至尝试在聚会上设置一个排他锁,所以最多只有一个线程可以同时在同一张表上收集统计信息,但没有任何改变。

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

难道我做错了什么?它是甲骨文的错误吗?我如何保证所有统计信息始终是最新的,并开启了增量统计功能,并且具有高水平的并发性?

4

6 回答 6

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”和“GLOBAL AND PARTITION”是否在某些方面有所不同,因为增量和近似都做基本相同的事情:聚合统计数据和直方图而不进行全面扫描。

于 2015-03-25T14:57:10.483 回答
1

您是否尝试过增量统计,但仍显式命名要分析的分区?

 dbms_stats.gather_table_stats(ownname=>myschema,
                              tabname=>big_table,
                              partname=>part,
                              degree=>dbms_stats.auto_degree);
于 2015-03-19T22:00:54.773 回答
1

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

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

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

于 2015-03-20T21:00:58.783 回答
1

考虑到您要实现的目标,您需要在所有分区的特定时间间隔上运行统计信息,而不是在加载每个分区的过程结束时运行。如果这是一个实时表并且全天候发生恒定的数据负载,这可能具有挑战性,但由于这些是 LARGE 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');

于 2015-03-24T21:49:37.150 回答
0

请检查您是否使用 DBMS_STATS 设置表首选项以收集增量统计信息。该 oracle 博客解释说,将在受影响的每一行之后收集统计信息。

增量统计维护需要收集任何将更改全局或表级统计信息的分区的统计信息。例如,在表中插入或更新一行后,列的最小值或最大值可能会发生变化

BEGIN 
DBMS_STATS.SET_TABLE_PREFS(myschema,'BIG_TABLE','INCREMENTAL','TRUE'); 
END;
于 2015-03-23T14:25:39.673 回答
-1

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

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

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

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

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

于 2015-03-04T17:55:04.513 回答