我每天都有表 ABC 间隔分区。每个分区用于特定日期的查询。即使我每天安排工作@nyt来收集统计数据,然后在统计数据收集之前使用该表的查询也不会使用最佳计划。
答案 0 :(得分:0)
收集分区表上的优化程序统计信息并不是一项简单的任务,但有一些注意事项。 特别是在每日分区架构上,它可能不是每天收集一次分区统计信息的最佳解决方案。
为了演示它,假设我们没有每日模式,而是每年的事务数据分区。问题是, 可以在1月1日(或1月6日或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);
必须避免的最坏情况是*表明分区为空的统计信息,但(同时)分区是大量填充的。
设置统计
这种方法假定&#34;正确&#34;已知查询的访问路径。在我们的例子中
我们可以访问一个带有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');
换句话说,在创建分区后立即启动统计信息,就像完整填充的分区一样。
答案 1 :(得分:0)
在我的应用程序中,我每天通过调度程序作业运行此过程一次。它收集最近分区的统计信息。
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 :(得分:0)
统计数据应作为显着更改数据的任何流程的一部分进行收集。不要依赖夜间工作来收集统计数据,尤其是在大型数据仓库中。
仅在夜间工作中收集统计数据有许多潜在的缺点:
将统计信息作为数据加载过程的一部分进行收集有许多潜在的优势。由于您比某些通用夜间统计工作更了解流程和表格,因此您可以利用许多高级功能:
不要将统计数据外包给其他预定的工作。统计数据非常重要和棘手,它们应该与任何正在进行重大数据更改的程序完全集成。