【问题标题】:How can I speed up queries against huge data warehouse tables with effective-dated data?如何加快对具有有效日期数据的大型数据仓库表的查询?
【发布时间】:2011-03-08 00:09:53
【问题描述】:

所以我正在查询一些非常大的表。它们如此大的原因是因为 PeopleSoft 每次对某些数据进行更改时都会插入新记录,而不是更新现有记录。实际上,它的事务表也是一个数据仓库。

这需要其中包含嵌套选择的查询,以获取最新/当前行。它们都具有生效日期,并且在每个日期(转换为一天)内,它们都可以具有有效的序列。因此,为了获得user_id=123 的当前记录,我必须这样做:

select * from sometable st
where st.user_id = 123
and st.effective_date = (select max(sti.effective_date) 
  from sometable sti where sti.user_id = st.user_id)
and st.effective_sequence = (select max(sti.effective_sequence) 
  from sometable sti where sti.user_id = st.user_id
  and sti.effective_date = st.effective_date)

这些表上有大量的索引,我找不到可以加快查询速度的其他任何东西。

我的麻烦是,我经常想从这些表中获取大约 50 个 user_id 的个人数据,但是当我将其中只有几条记录的表与其中一些 PeopleSoft 表连接起来时,事情就变成了废话。

PeopleSoft 表位于我通过数据库链接访问的远程数据库上。我的查询往往如下所示:

select st.* from local_table lt, sometable@remotedb st
where lt.user_id in ('123', '456', '789')
and lt.user_id = st.user_id
and st.effective_date = (select max(sti.effective_date) 
  from sometable@remotedb sti where sti.user_id = st.user_id)
and st.effective_sequence = (select max(sti.effective_sequence) 
  from sometable@remotedb sti where sti.user_id = st.user_id
  and sti.effective_date = st.effective_date)

当我必须将多个 PeopleSoft 表与我的本地表连接时,情况会变得更糟。性能简直无法接受。

我可以做些什么来提高性能?我已经尝试过查询提示,以确保我的本地表首先加入到其在 PeopleSoft 中的合作伙伴,因此它不会在将其缩小到正确的 user_id 之前尝试将其所有表连接在一起。我已经尝试了LEADING 提示并玩弄了试图将处理推送到远程数据库的提示,但是解释计划被掩盖了,只是对几个操作说“远程”,我不知道发生了什么开。

假设我无权更改 PeopleSoft 和我的表格的位置,提示是我的最佳选择吗?如果我要加入一个包含四个远程表的本地表,并且本地表与其中两个表连接,我将如何格式化提示,以便我的本地表(非常小 - 事实上,我可以做一个内联视图让我的本地表只是我感兴趣的 user_ids)首先与每个远程表连接?

编辑:应用程序需要实时数据,所以不幸的是,物化视图或其他缓存数据的方法是不够的。

【问题讨论】:

  • +1,任何遇到过通过 DB 链接加入的人都应该能够理解这一点。
  • 你能发布这些查询的解释计划吗?
  • 如果您的数据库支持即时更新,您也许可以使用物化视图。

标签: sql oracle data-warehouse peoplesoft


【解决方案1】:

像这样重构查询是否有帮助?

SELECT *
  FROM (SELECT st.*, MAX(st.effective_date) OVER (PARTITION BY st.user_id) max_dt,
                     MAX(st.effective_sequence) OVER (PARTITION BY st.user_id, st.effective_date) max_seq
          FROM local_table lt JOIN sometable@remotedb st ON (lt.user_id = st.user_id)
         WHERE lt.user_id in ('123', '456', '789'))
 WHERE effective_date = max_dt
   AND effective_seq = max_seq;

我同意 @Mark Ba​​ker 的观点,即通过 DB Links 加入的性能确实很糟糕,而且您可能会受限于使用这种方法所能完成的工作。

【讨论】:

  • 我不知道这在内部是如何工作的,但它让我比原来的查询获得了显着的性能提升。
  • 我不确定,但我的直觉是你的 where 子句中的每个子查询都会导致对远程数据库的单独查询,而这个查询一键获取所有内容。
【解决方案2】:

一种方法是将 PL/SQL 函数粘贴到所有内容中。 举个例子

create table remote (user_id number, eff_date date, eff_seq number, value varchar2(10));

create type typ_remote as object (user_id number, eff_date date, eff_seq number, value varchar2(10));
.
/

create type typ_tab_remote as table of typ_remote;
.
/

insert into remote values (1, date '2010-01-02', 1, 'a');
insert into remote values (1, date '2010-01-02', 2, 'b');
insert into remote values (1, date '2010-01-02', 3, 'c');
insert into remote values (1, date '2010-01-03', 1, 'd');
insert into remote values (1, date '2010-01-03', 2, 'e');
insert into remote values (1, date '2010-01-03', 3, 'f');

insert into remote values (2, date '2010-01-02', 1, 'a');
insert into remote values (2, date '2010-01-02', 2, 'b');
insert into remote values (2, date '2010-01-03', 1, 'd');

create function show_remote (i_user_id_1 in number, i_user_id_2 in number) return typ_tab_remote pipelined is
    CURSOR c_1 is
    SELECT user_id, eff_date, eff_seq, value
    FROM
        (select user_id, eff_date, eff_seq, value, 
                        rank() over (partition by user_id order by eff_date desc, eff_seq desc) rnk
        from remote
        where user_id in (i_user_id_1,i_user_id_2))
    WHERE rnk = 1;
begin
    for c_rec in c_1 loop
        pipe row (typ_remote(c_rec.user_id, c_rec.eff_date, c_rec.eff_seq, c_rec.value));
    end loop;
    return;
end;
/

select * from table(show_remote(1,null));

select * from table(show_remote(1,2));

您可以将它们加载到本地表(例如全局临时表)中,而不是将 user_id 作为参数单独传递。然后 PL/SQL 将循环遍历表,对本地表中的每一行进行远程选择。没有一个查询会同时具有本地和远程表。实际上,您将编写自己的加入代码。

【讨论】:

    【解决方案3】:

    一种选择是首先使用公用表表达式具体化查询的远程部分,这样您就可以确保从远程数据库中仅获取相关数据。另一个改进是将针对远程数据库的 2 个子查询合并为一个分析基于函数的子查询。这样的查询也可以在您当前的查询中使用。玩过db后才能提出其他建议。

    见下文

    with remote_query as
    (
        select /*+ materialize */  st.* from sometable@remotedb st
        where st.user_id in ('123', '456', '789')
        and st.rowid in( select first_value(rowid) over (order by effective_date desc, 
                             effective_sequence desc ) from sometable@remotedb st1 
                          where st.user_id=st1.user_id)
    )
    
    select lt.*,st.* 
    FROM local_table st,remote_query rt
    where st.user_id=rt.user_id
    

    【讨论】:

    • +1,喜欢你对分析的使用。我认为你可能在假设一个硬编码的 user_id 列表可以在你的 WITH 子句中指定时有点作弊 - 如果它那么简单,你首先不需要连接到 local_table。
    【解决方案4】:

    您没有提到数据新鲜度的要求,但一种选择是创建物化视图(您将被限制为刷新完成,因为您无法在源系统中创建快照日志)仅针对事务表的当前版本化行的数据。这些物化视图表将驻留在您的本地系统中,并且可以向它们添加额外的索引以提高查询性能。

    【讨论】:

    • 我喜欢这个想法,但数据需要是实时的。
    • 然后考虑远程数据库中的物化视图,其中列出了每个用户标识的最新行。这将减少通过远程链接移动的数据量。
    【解决方案5】:

    性能问题将是跨链接的访问。通过对本地表的部分查询,它全部在本地执行,因此无法访问远程索引,它会将所有远程数据拉回以进行 lkocally 测试。

    如果您可以在本地数据库中使用从 peoplesoft 数据库定期(每晚)刷新的历史数据的物化视图,则仅访问远程 peoplesoft 数据库以获取今天的更改(在 where 子句中添加 Effective_date = today)并合并两个查询。

    另一种选择可能是使用 INSERT INTO X SELECT FROM 仅用于远程数据将其拉入临时本地表或物化视图,然后使用第二个查询将其与本地数据连接...类似于 josephj1989 的建议

    或者(尽管可能存在许可问题)尝试 RAC 将本地数据库与远程 peoplesoft 数据库进行集群。

    【讨论】:

      【解决方案6】:

      您可以试试这个,而不是使用子查询。我不知道 Oracle 在这方面是否会表现得更好,因为我不怎么使用 Oracle。

      SELECT
          ST1.col1,
          ST1.col2,
          ...
      FROM
          Some_Table ST1
      LEFT OUTER JOIN Some_Table ST2 ON
          ST2.user_id = ST1.user_id AND
          (
              ST2.effective_date > ST1.effective_date OR
              (
                  ST2.effective_date = ST1.effective_date AND
                  ST2.effective_sequence > ST1.effective_sequence
              )
          )
      WHERE
          ST2.user_id IS NULL
      

      另一种可能的解决方案是:

      SELECT
          ST1.col1,
          ST1.col2,
          ...
      FROM
          Some_Table ST1
      WHERE
          NOT EXISTS
          (
              SELECT
              FROM
                  Some_Table ST2
              WHERE
                  ST2.user_id = ST1.user_id AND
                  (
                      ST2.effective_date > ST1.effective_date OR
                      (
                          ST2.effective_date = ST1.effective_date AND
                          ST2.effective_sequence > ST1.effective_sequence
                      )
                  )
          )
      

      【讨论】:

        【解决方案7】:

        是否可以选择创建一个数据库,用于存储可以每晚更新的非仓储类型的东西?如果是这样,您可以创建一个仅移动最新记录的夜间进程。这将摆脱您为每天查询所做的 MAX 工作,并显着减少数量或记录。

        另外,取决于您能否在最新数据和可用数据之间间隔 1 天。

        我对 Oracle 不是很熟悉,所以可能有一种方法可以通过更改查询来获得改进...

        【讨论】:

          【解决方案8】:

          您能否将具有所需 user_id 的行 ETL 到您自己的表中,只创建所需的索引来支持您的查询并在其上执行您的查询?

          【讨论】:

            【解决方案9】:

            PeopleSoft 表是交付的,还是定制的?你确定这是一张物理表,而不是PS端写得不好的视图吗?如果它是您要反对的交付记录(示例看起来很像 PS_JOB 或引用它的视图),也许您可​​以指出这一点。 PS_JOB 是一个提供大量索引的野兽,而且大多数网站都添加了更多索引。

            如果您知道表上的索引,您可以使用 Oracle 提示指定要使用的首选索引;这有时会有所帮助。

            您是否制定了解释计划以查看是否可以确定问题出在哪里?也许有笛卡尔连接、全表扫描等?

            【讨论】:

              【解决方案10】:

              在我看来,您正在处理数据仓库中的类型 2 维度。有几种方法可以实现类型 2 维度,主要有像 ValidFrom, ValidTo, Version, Status 这样的列。并非所有这些都始终存在,如果您可以发布表的架构会很有趣。以下是它的外观示例(约翰·史密斯于 2010 年 6 月 24 日从印第安纳州搬到俄亥俄州)

              UserKey  UserBusinessKey State    ValidFrom    ValidTo   Version  Status
              7234     John_Smith_17   Indiana  2005-03-20  2010-06-23    1     expired
              9116     John_Smith_17   Ohio     2010-06-24  3000-01-01    2     current
              

              获取一行的最新版本,一般使用

              WHERE Status = 'current'
              

              WHERE ValidTo = '3000-01-01'
              

              请注意,这个在遥远的未来有一些常数。

              WHERE ValidTo > CURRENT_DATE
              

              您的示例似乎使用ValidFrom(有效日期),因此您必须找到max() 才能找到最新的行。看看架构——你的表中是否有 Status or ValidTo 等价物?

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-02-25
                • 1970-01-01
                • 1970-01-01
                • 2014-02-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多