3

我处于某种 DWH 项目中(不完全是,但仍然如此)。我们经常遇到这个问题,我想知道是否会有更好的解决方案。关注

我们收到一些包含用户所有状态记录的大文件,例如:

UID | State    | Date
1   | Active   | 20120518
2   | Inactive | 20120517
1   | Inactive | 20120517
...

我们通常只对每个用户的最新状态感兴趣。到目前为止一切顺利,只需进行一点排序,我们就可以得到我们想要的方式。唯一的问题是,这些文件通常很大.. 像 20-60gb,排序这些家伙有时很痛苦,因为排序的逻辑通常不是那么简单。

我们通常所做的是将所有内容加载到我们的 Oracle 中,并使用中间表和物化视图来完成。尽管如此,有时性能会咬我们。

20-60GB可能很大,但不是那么大。我的意思是,应该是一种更专业的方式来处理这些记录,不是吗?

我想有两种基本的方法来解决这个问题:

1) 在 DBMS、脚本和编译的东西之外编程。但也许这不是很灵活,除非投入更多的时间来开发一些东西。另外,我可能不得不忙于自己管理盒子资源,而我不想为此担心。

2)将所有内容加载到DBMS(在我的例子中是Oracle)并使用它提供的任何工具对数据进行排序和剪辑。不过,这就是我的情况,我不确定我们是否使用了所有工具,或者只是以适合 Oracle 10g 的正确方式进行操作。

那么问题是:

您有一个 60gb 的文件,其中包含数百万条历史记录,例如上面的记录,您的用户希望在 DB 中有一个表,其中包含每个用户的最后状态。

你们会怎么做?

谢谢!

4

4 回答 4

2

你可以做两件事来加快这个过程。

第一件事是向它投入计算能力。如果您有企业版和大量内核,您将通过并行查询显着减少加载时间。

另一件事是避免加载您不想要的记录。这就是您提到预处理文件的原因。我不确定您可以在那里做很多事情,除非您可以访问 Hadoop 集群以在您的文件上运行一些 map-reduce 作业(嗯,主要是 reduce,您发布的结构与已经映射的结构差不多)。

但是还有一种选择:外部表。外部表是其数据在 OS 文件而不是表空间中的表。并且它们可以并行启用(前提是您的文件符合某些条件)。 了解更多

所以,你可能有一个像这样的外部表

CREATE TABLE user_status_external (
   uid     NUMBER(6),
   status      VARCHAR2(10),
   sdate        DATE
ORGANIZATION EXTERNAL
(TYPE oracle_loader
 DEFAULT DIRECTORY data_dir
 ACCESS PARAMETERS
 (
  RECORDS DELIMITED BY newline
  BADFILE 'usrsts.bad'
  DISCARDFILE 'usrsts.dis'
  LOGFILE 'usrsts.log'
  FIELDS TERMINATED BY ","  OPTIONALLY ENCLOSED BY '"'
  (
   uid     INTEGER EXTERNAL(6),
   status     CHAR(10),
   sdate       date 'yyyymmdd' )
 )
 LOCATION ('usrsts.dmp')
)
PARALLEL
REJECT LIMIT UNLIMITED;

请注意,您需要对 DATA_DIR 目录对象具有读写权限。

创建外部表后,您可以使用插入语句将唯一需要的数据加载到目标表中:

insert into user_status (uid, status, last_status_date)
    select  sq.uid
            ,  sq.status
            ,  sq.sdate
    from (
        select /*+ parallel (et,4) */ 
               et.uid
               , et.status
               , et.sdate
               , row_number() over (partition by et.uid order by et.sdate desc) rn  
        from user_status_external et
        ) sq
    where sq.rn = 1

请注意,与所有性能建议一样,没有任何保证。您需要对环境中的事物进行基准测试。

另一件事是使用 INSERT:我假设这些都是新的 USERID,因为这是您的帖子所建议的场景。如果您有一个更复杂的场景,那么您可能想要查看 MERGE 或完全不同的方法。


最后一件事:您似乎假设这是一种常见情况,它有一些标准方法。但是大多数数据仓库会加载他们获得的所有数据。然后,他们可能会针对各种不同的用途、数据集市等对其进行过滤。但他们几乎总是在所有不同记录的实际仓库中维护历史记录。这就是为什么您可能无法获得行业标准解决方案的原因。

于 2012-05-18T12:41:57.280 回答
1

我会按照 APC 作为第一次尝试所说的那样去做。但是,我认为并行表只能在数据位于多个文件中的情况下并行加载数据,因此您可能必须将文件分成几个文件。文件是如何生成的?处理 20 - 60GB 的文件真的很痛苦——你能改变文件的生成,比如得到 X 2GB 的文件吗?

将所有记录放入数据库后,您可能会在尝试对 60GB 数据进行排序时遇到问题 - 值得查看您用于提取最新状态的查询的排序阶段。过去,我通过对要排序的字段之一(在本例中为 user_id)上的数据进行哈希分区来帮助进行大型排序。然后 Oracle 只需要进行 X 次较小的排序,每个排序都可以并行进行。

所以,我的想法是:

  1. 尝试生成许多较小的文件而不是 1 个大文件
  2. 使用外部表,看看是否可以直接从外部表中提取你想要的数据
  3. 如果没有,则将文件的全部内容加载到哈希分区表中 - 在此阶段确保执行 insert /*+ append nologging */ 以避免生成撤消和重做。如果您的数据库将 force_logging 设置为 true,则 nologging 提示将无效。
  4. 对暂存数据运行选择以仅提取您关心的行,然后丢弃暂存数据。

nologging 选项可能对您获得良好的性能至关重要 - 要加载 60GB 的数据,您将生成至少 60GB 的重做日志,所以如果可以避免这种情况,那就更好了。您可能需要与您的 DBA 讨论一下!

假设您有大量可用的 CPU,在将数据批量加载到临时表中时压缩数据也可能是有意义的。如果它有重复的字段,压缩可能是磁盘上数据大小的一半——写入时节省的磁盘 IO 通常超过加载时消耗的任何额外 CPU。

于 2012-05-18T13:34:31.210 回答
0

我可能过于简单化了这个问题,但为什么不这样:

create materialized view my_view
tablespace my_tablespace
nologging
build immediate
refresh complete on demand
with primary key
as
select uid,state,date from
(
  select /*+ parallel (t,4) */ uid, state, date, row_number() over (partition by uid order by date desc) rnum
  from my_table t;
)
where rnum = 1;

然后在需要时完全刷新。

编辑:任何人都不要忘记重建统计数据,并可能在 uid 上抛出一个唯一索引。

于 2012-05-18T11:22:25.340 回答
-1

我会编写一个程序来遍历每条记录,并只保留那些比以前看到的记录更新的记录。最后,将数据插入数据库。

这有多实用取决于我们谈论的用户数量——你最终可能不得不仔细考虑你的中间存储。

一般来说,这变成(在伪代码中):

foreach row in file
    if savedrow is null
        save row
    else
        if row is more desirable than savedrow
             save row
        end
    end
end

send saved rows to database

重点是,您需要定义如何认为一行比另一行更可取。在简单的情况下,对于给定的用户,当前行的日期晚于我们保存的最后一行。最后,您将有一个行列表,每个用户一个,每个行都有您看到的最近日期。

您可以通用脚本或程序,以便框架与理解每个数据文件的代码分开。

仍然需要一段时间,请注意:-)

于 2012-05-18T10:18:41.773 回答