当一组相关的 Oracle 表没有时间信息时,按天对它们进行分区

Posted

技术标签:

【中文标题】当一组相关的 Oracle 表没有时间信息时,按天对它们进行分区【英文标题】:Partitioning a related set of Oracle tables by day when they don't all have Time information 【发布时间】:2015-02-12 21:14:34 【问题描述】:

我有一组看起来像这样的表:

Time_Table(比较小):

Time      (TIMESTAMP)
timeId    (NUMBER)
Data...   (NUMBER)

Table2(大,每个 time_table 行大约 30 行):

timeId    (NUMBER)
table2Id  (NUMBER)
Data...   (NUMBER)

Table3(非常大,每 table2 行大约 10 行,目前几百天后有 14 亿行):

timeId    (NUMBER)
table2Id  (NUMBER)
table3Id  (NUMBER)
Data...   (NUMBER)

我的查询至少总是在 timeId 上加入,并且每个查询都被分解为几天(10 天的阅读将导致 10 个较小的查询)。每天都有新数据写入所有表。我们需要从这些表中存储(和查询)多年的数据。

当时间信息只能通过 JOIN 获知时,如何将这些表划分为每日块?我应该以不依赖于时间的方式来看待分区吗?这可以自动完成,还是必须手动完成?

Oracle 11.2 版

【问题讨论】:

您使用的是什么版本的 Oracle?答案在 10.2、11.2 和 12.1 中可能会有所不同。 请显示数据类型和示例数据。 @OldProgrammer 所有类型都是数字,时间是时间戳 @JustinCave,我们使用的是 v11.2 那么table3每天有多少行?一天十行似乎微不足道。 【参考方案1】:

参考分区在这里可能会有所帮助。它允许子表的分区方案由父表决定。

架构

--drop table table3;
--drop table table2;
--drop table time_table;

drop table time_table;
create table Time_Table
(
    time   TIMESTAMP,
    timeId NUMBER,
    Data01 NUMBER,
    constraint time_table_pk primary key (timeId)
)
partition by range (time)
(
    partition p1 values less than (date '2000-01-02'),
    partition p2 values less than (date '2000-01-03'),
    partition p3 values less than (date '2000-01-04')
);

create table table2
(
    timeId   number,
    table2Id number,
    Data01   number,
    constraint table2_pk primary key (table2ID),
    constraint table2_fk foreign key (timeId) references time_table(timeId)
);


create table table3
(
    timeId   number not null,
    table2Id number,
    table3Id number,
    Data01   number,
    constraint table3_pk primary key (table3ID),
    constraint table3_fk1 foreign key (timeId) references time_table(timeId),
    constraint table3_fk2 foreign key (table2ID) references table2(table2ID)
) partition by reference (table3_fk1);

执行计划

PstartPstop 表明即使分区谓词仅在小父表上设置,也正确修剪了大子表。

explain plan for
select *
from table3
join time_table using (timeId)
where time = date '2000-01-02';

select * from table(dbms_xplan.display);

Plan hash value: 832465087

-----------------------------------------------------------------------------------------------------
| Id  | Operation              | Name       | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |            |     1 |    91 |     3   (0)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE SINGLE|            |     1 |    91 |     3   (0)| 00:00:01 |     2 |     2 |
|   2 |   NESTED LOOPS         |            |     1 |    91 |     3   (0)| 00:00:01 |       |       |
|*  3 |    TABLE ACCESS FULL   | TIME_TABLE |     1 |    39 |     2   (0)| 00:00:01 |     2 |     2 |
|*  4 |    TABLE ACCESS FULL   | TABLE3     |     1 |    52 |     1   (0)| 00:00:01 |     2 |     2 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - filter("TIME_TABLE"."TIME"=TIMESTAMP' 2000-01-02 00:00:00')
   4 - filter("TABLE3"."TIMEID"="TIME_TABLE"."TIMEID")

Note
-----
   - dynamic sampling used for this statement (level=2)
   - automatic DOP: skipped because of IO calibrate statistics are missing

警告

参考分区有一些怪癖。它不适用于 11g 中的间隔分区,因此您必须手动为父表定义每个分区。外键也无法禁用,这可能需要修改一些脚本。和任何很少使用的功能一样,它也有一些错误。

【讨论】:

我不推荐该解决方案,因为它需要 GLOBAL 索引并且仅支持 2 级主详细信息。它还需要 FK,这对于仓库来说可能是个问题(APPEND 插入不起作用)。相反,我可以建议重新设计。什么是时间ID?它是来自序列的价值吗?为什么?时间是天然的独特价值 - 使用它而不是无意义的生成数字。 @Rusty 你说得对,引用分区有一些明显的缺点。尽管APPEND does work 即使有外键。如果仅添加一列并且表已经“胖”,则非规范化可能是更好的解决方案。 我们曾经使用时间戳作为PK,但实际上可以有重复的时间,这是有效的数据。我还能保留“TimeId”但将 Time 复制到其他表中吗? @SamuelO'Malley 当然,这可能是有道理的。对于仅使用时间戳的简单情况,它使查询和分区修剪更容易。它还允许在其他查询中使用其他时间维度值。不过,它确实需要大约 10GB 的存储空间来存储额外的一列。 @Jon Heller - 根据 Oracle 规范 docs.oracle.com/cd/E11882_01/server.112/e41084/… :目标表上不能定义任何触发器或引用完整性约束。所以附加提示将被忽略。【参考方案2】:
drop table time_table;
create table Time_Table
(
    time   TIMESTAMP,
--    timeId NUMBER, Why you need ID when you have timestamp?????
    Data01 NUMBER,
    constraint time_table_pk primary key (time) -- not timeID!!!
)
partition by range (time)
(
    partition p1 values less than (date '2000-01-02'),
    partition p2 values less than (date '2000-01-03'),
    partition p3 values less than (date '2000-01-04')
);

create table table2
(
    time     timestamp not null,
    table2ID number,
    Data01   number
)
partition by range (time)
(
    partition p1 values less than (date '2000-01-02'),
    partition p2 values less than (date '2000-01-03'),
    partition p3 values less than (date '2000-01-04')
);


create table table3
(
    time     timestamp not null,
    table2Id number,
    table3Id number,
    Data01   number
) 
partition by range (time)
(
    partition p1 values less than (date '2000-01-02'),
    partition p2 values less than (date '2000-01-03'),
    partition p3 values less than (date '2000-01-04')
);

【讨论】:

在将 TimeID 保留为 PK 的情况下是否可行?实际上,我们确实得到了具有相同时间戳但不同 ID 的行。 我根本不会使用从序列生成的值。它使数据无法在环境之间传输。您需要使用锁定机制来防止重复。等待时间少于 1 微秒。 另一个提示如何获取始终唯一的时间戳值。返回 systimestamp 值 - 通常它在 Unix 上的精度为 6,在 Windows 上为 3。将其修剪为 3 位数。然后,您可以在插入中使用会话级别全局变量计数器或 rownum 中的值作为选择操作。获取 MOD(rownum, 1000000) 并将此值添加到 systimestamp。因此,您可以在 1 毫秒内生成 100 万个唯一值。这应该足够了。示例:l_log_rec.log_time := l_log_rec.log_time + to_dsinterval('PT0.'||to_char(g_counter, 'fm000000009')||'S') 这是一个很好的提示,但不幸的是,我正在编写的 Times(和 Ids)来自我无法修改的系统中的诊断数据。事件可以在完全相同的时间发生(不是偶然,而是实际上是双重事件)。

以上是关于当一组相关的 Oracle 表没有时间信息时,按天对它们进行分区的主要内容,如果未能解决你的问题,请参考以下文章

oracle 如何按天分组?比如一个表有个字段ST(date 类型)精确到秒,如何使用ST按天分组?

Oracle扩展的统计信息

oracle索引

oracle--事物---

Oracle索引的原理及使用

oracle 事务