我是一个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中的表,每个用户都有最后一个状态。
你会怎么做?谢谢!
答案 0 :(得分:2)
您可以采取两项措施来加快这一进程。
首先要为它提供计算能力。如果您拥有Enterprise Edition和大量内核,则可以通过并行查询显着减少加载时间。
另一件事是避免加载你不想要的记录。这就是你提到预处理文件的原因。我不确定你能做多少事情,除非你有权访问Hadoop集群在你的文件上运行一些map-reduce作业(好吧,主要是减少你发布的结构就像映射一样)已经可以了。)
但还有另一种选择:外部表格。外部表是在OS文件中而不是表空间中包含数据的表。并且它们可以并行启用(提供您的文件符合某些标准)。 Find out more
所以,你可能有一个像这样的外部表
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语句将唯一需要的数据加载到目标表中:
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或完全不同的方法。
最后一件事:您似乎认为这是一种常见情况,它有一些标准方法。但是大多数数据仓库都会加载他们获得的所有数据。然后,他们可以过滤它以用于各种不同的用途,数据集市等。但是他们几乎总是在所有不同记录的实际仓库中维护历史。这就是为什么您可能无法获得行业标准解决方案的原因。
答案 1 :(得分:1)
我会选择APC首先说的话。但是,如果数据在多个文件中,我认为并行表只能并行加载数据,因此您可能需要将文件切割成多个。文件是如何生成的?一个20-60GB的文件真的很难处理 - 你能不能改变文件的生成,以便获得X 2GB文件?
将所有记录存入数据库后,您可能会遇到尝试对60GB数据进行排序的问题 - 值得查看用于提取最新状态的查询的排序阶段。在过去,我通过对要排序的字段之一上的数据进行散列分区来帮助大量排序,在本例中为user_id。然后Oracle只需做X个较小的排序,每个排序都可以并行进行。
所以,我的想法是:
nologging选项对于获得良好性能可能至关重要 - 要加载60GB的数据,您将生成至少60GB的重做日志,因此如果可以避免,那就更好了。您可能需要与您的DBA聊聊!
假设您有大量可用的CPU,在将数据批量加载到临时表时压缩数据也是有意义的。如果压缩有重复的字段,压缩可能是磁盘上数据大小的一半 - 写入时保存的磁盘IO通常比加载它时消耗的额外CPU要多。
答案 2 :(得分: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上抛出唯一索引。
答案 3 :(得分:-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
重点是,您需要定义一行被认为比另一行更合适。在简单的情况下,对于给定用户,当前行的日期晚于我们保存的最后一行。最后,您将拥有一个行列表,每个用户一个,每个行都有您最近看到的日期。
您可以对脚本或程序进行通用,以便框架与理解每个数据文件的代码分开。
它还需要一段时间,请注意: - )