我正在 Oracle 11g 上开发 DWH。我们有一些大表(250+ 百万行),按值分区。每个分区分配给不同的馈送源,每个分区相互独立,因此可以同时加载和处理。
数据分布很不均匀,我们有百万行的分区,也有不超过百行的分区,但是我没有选择分区方案,顺便也改不了。
考虑到数据量,我们必须保证每个分区总是有最新的统计数据,因为如果后续的细化没有对数据的最佳访问,它们将永远持续下去。
所以对于每个并发的 ETL 线程,我们
- 截断分区
- 从暂存区加载数据
SELECT /*+ APPEND */ INTO big_table PARTITION(part1) FROM temp_table WHERE partition_colum = PART1
(这样我们有直接的路径,我们不锁定整个表)
- 我们收集修改后的分区的统计信息。
在项目的第一阶段,我们使用了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 个同时启动的大分区,它们也将几乎同时完成加载阶段。
第一个线程结束插入语句、提交并启动统计信息收集。stats 程序注意到有 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 过程在第二次被调用时,应该检查第二个分区上的最后一次提交,并且应该看到它比最后一次统计数据收集时间更新。但似乎它没有发生。
难道我做错了什么?它是甲骨文的错误吗?我如何保证所有统计信息始终是最新的,并开启了增量统计功能,并且具有高水平的并发性?