【问题标题】:Sequential vs parallel solution顺序与并行解决方案
【发布时间】:2018-01-27 16:38:42
【问题描述】:

我会尽量简化我的问题。

假设我们在 Oracle 11g 中有 3 个表。

Persons (person_id, name, surname, status, etc )
Actions (action_id, person_id, action_value, action_date, calculated_flag) 
Calculations (calculation_id, person_id,computed_value,computed_date)

我想要的是每个人符合特定标准(比如说status=3) 我应该从Actions 表中获得action_valuessum,其中calculated_flag=0。 (类似select sum(action_value) from Actions where calculated_flag=0 and person_id=current_id)。

然后我将在某种公式中使用 sum 并为特定的 person_id 更新 Calculations 表。

update Calculations set computed_value=newvalue, computed_date=sysdate
where  person_id=current_id

之后,参与行的calculated_flag 将设置为1

update Actions set calculated_flag=1
where  calculated_flag=0 and person_id=current_id

现在这可以很容易地按顺序完成,方法是创建一个游标,该游标将遍历 Persons 表,然后执行特定人员所需的每个操作。

(我不提供顺序解决方案的代码,因为上面只是一个类似于我的实际设置的示例。)

问题是我们正在谈论大量的数据,而顺序方法似乎浪费了计算时间。

在我看来,这个任务可以针对多个 person_id 并行执行。

所以问题是:

这种任务可以在 PL/SQL 中使用并行化来执行吗?

解决方案会是什么样子?也就是说,应该使用哪些特殊的包(例如DBMS_PARALLEL_EXECUTE)、关键字(例如bulk collect)、方法以及以什么方式使用?

另外,我应该担心并行更新的部分失败吗?

请注意,我不太熟悉使用 PL/SQL 进行并行编程。 谢谢。

编辑 1. 这是我的顺序解决方案的伪代码

procedure sequential_solution is
    cursor persons_of_interest is
        select person_id from persons
        where  status = 3;

    tempvalue number;
    newvalue  number;
begin

    for person in persons_of_interest
    loop
        begin
            savepoint personsp;

            --step 1
            select sum(action_value) into tempvalue
            from   actions
            where  calculated_flag = 0
            and    person_id = person.person_id;

            newvalue := dosomemorecalculations(tempvalue);

            --step 2
            update calculations set computed_value = newvalue, computed_date  = sysdate
            where  person_id = person.person_id;

            --step 3
            update actions set calculated_flag = 1;
            where  calculated_flag = 0 and person_id = person.person_id;

            --step 4 (didn't mention this step before - sorry)
            insert into actions
                ( person_id, action_value, action_date, calculated_flag )
            values
                ( person.person_id, 100, sysdate, 0 );
        exception
            when others then
                rollback to personsp;
                -- this call is defined with pragma AUTONOMOUS_TRANSACTION:
                log_failure(person_id);
        end;

    end loop;

end;

现在,我将如何使用forallbulk colletct 或并行编程来加快上述速度在以下约束下:

  1. 适当的内存管理(考虑到大量数据)
  2. 对于一个人,如果步骤序列的一部分失败 - 所有步骤都应该回滚并记录失败。

【问题讨论】:

  • 并行处理是一种极端的解决方案。您是否考虑过基于集合的解决方案而不是游标循环?还是bulk collectforall
  • @TonyAndrews 这是一个想法......虽然我不太确定如何解决 1. 可能存在巨大的内存消耗(可能使用 limit 会解决这个问题)2.通过顺序方法,我可以按人提交。在这种情况下,如果一个人的操作失败 - 其他人不受影响 - 不太确定如何使用 bulk updates 实现这一目标,因为每个人更新了多个表格。您提到的“基于集合的解决方案”是否与 bulk collectfor all 的使用相同,还是其他?您能否以伪代码提供您提出的解决方案?谢谢。
  • @TonyAndrews 为什么要避免并行解决方案?我对特定问题思考得越多,我就越相信使用forall 和基于集合的解决方案进行类似事务的操作(每人)可能会非常麻烦。我在这里错过了什么吗?
  • 在 25 年的 Oracle 开发中,我很少使用任何并行处理。它通常只在巨大的,可能是数据仓库,数据库中完成,并且有问题的工作是唯一在具有多个 CPU 的服务器上运行的东西(系统上没有其他用户同时)。这是你的情况吗? “相当大量的数据”是多少? 数十亿行?
  • @TonyAndrews 好的,所以行数肯定比数十亿少得多......可能有一百万(我稍后会给出一个估计)......但是你的其他标准提都种遇见。也就是说 - 此过程将单独运行(仅形成一个用户)并且每年仅运行两次或 4-5 次,而没有其他用户登录数据库。现在我倾向于并行解决方案,因为它看起来直观更干净的解决方案。此外,我仍然无法想象如何使用bulk collect 进行事务管理for all 和其他基于集合的解决方案。

标签: oracle plsql parallel-processing


【解决方案1】:

我可以提出以下建议。假设您在 persons 表中有 1 000 000 行,并且您希望每次迭代处理 10 000 人。所以你可以这样做:

declare
  id_from persons.person_id%type;
  id_to persons.person_id%type;
  calc_date date := sysdate;
begin
    for i in 1 .. 100 loop
      id_from := (i - 1) * 10000;
      id_to := i * 10000;

      -- Updating Calculations table, errors are logged into err$_calculations table
      merge into Calculations c
      using (select p.person_id, sum(action_value) newvalue
               from Actions a join persons p on p.person_id = a.person_id
              where a.calculated_flag = 0 
                and p.status = 3
                and p.person_id between id_from and id_to
              group by p.person_id) s
         on (s.person_id = c.person_id)
      when matched then update
       set c.computed_value = s.newvalue, 
           c.computed_date = calc_date
       log errors into err$_calculations reject limit unlimited;

      -- updating actions table only for those person_id which had no errors:
      merge into actions a
      using (select distinct p.person_id
               from persons p join Calculations c on p.person_id = c.person_id
              where c.computed_date = calc_date
                and p.person_id between id_from and id_to)
         on (c.person_id = p.person_id)
       when matched then update
       set a.calculated_flag = 1;

      -- inserting list of persons for who calculations were successful
      insert into actions (person_id, action_value, action_date, calculated_flag)
       select distinct p.person_id, 100, calc_date, 0
         from persons p join Calculations c on p.person_id = c.person_id
        where c.computed_date = calc_date
          and p.person_id between id_from and id_to;

      commit;
    end loop;
end;

它是如何工作的:

  • 您将persons 表中的数据拆分为大约 10000 行的块(取决于 ID 数量的差距,i * 10000 的最大值应该大于person_id 的最大值)
  • 您在MERGE 语句中进行计算并更新Calculations
  • LOG ERRORS 子句防止异常。如果发生错误,出现错误的行将不会被更新,但会被插入到表中进行错误记录。执行不会中断。要创建此表,请执行:

    begin
      DBMS_ERRLOG.CREATE_ERROR_LOG('CALCULATIONS');
    end;
    

    将创建表err$_calculations。有关DBMS_ERRLOG 包的更多信息,请参阅documentation

  • 第二条MERGE 语句仅对未发生错误的行设置calculated_flag = 1INSERT 语句将这些行插入到actions 表中。只需使用 Calculations 表中的 select 即可找到这些行。
  • 另外,我添加了变量id_fromid_to 来计算要更新的ID 范围,并添加了变量calc_date 以确保在第一个MERGE 语句中更新的所有行都可以在以后按日期找到。李>

【讨论】:

  • 感谢您的回复。不幸的是,我认为我不能将所有步骤放在一个语句中(如上面的合并)。在您的解决方案中Actionscalculated_flag 永远不会更新为1。还有第四步(insert)我忘了提。看看我编辑的问题版本,我为顺序解决方案添加了一些代码......
  • @Plirkee 添加更多语句没有问题。我更新了我的答案(代码和解释),看看。
  • 好的,我明白了......但是我的第二个约束仍然没有得到满足。假设第一次合并完美执行(没有错误),但第二次合并或第三次插入由于某种原因(对于某些人)失败。然后我手上就会出现矛盾。我希望你明白我想说什么......每个人的这 4 个步骤应该表现为一个交易 - 全部或全部。如果对于许多人来说,他们的交易失败,我没有什么大问题。但不能有特定人的部分失败。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-31
  • 1970-01-01
  • 2013-04-26
  • 1970-01-01
  • 2019-07-08
  • 1970-01-01
相关资源
最近更新 更多