收集分区表上的优化器统计数据并非易事,但需要注意一些事项。
特别是在每日分区模式上,它可能不是每天收集一次分区统计信息的最佳解决方案。
为了证明这一点,假设我们没有每日模式,而是每年的交易数据分区。问题是,
可以在 1 月 1 日(或 1 月 1 日或 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);
必须避免的最坏情况是 * 指出分区为空的统计信息,但(同时)该分区被大量填充。
设置统计数据
这种方法假定查询的“正确”访问路径是已知的。在我们的例子中
我们可以使用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');
换句话说,在创建分区后立即启动统计信息,就像填充完整的分区一样。