PL/SQL 加速长函数

Posted

技术标签:

【中文标题】PL/SQL 加速长函数【英文标题】:PL/SQL Speeding up long function 【发布时间】:2018-07-09 20:53:06 【问题描述】:

我希望加快以下 PL/SQL 函数的速度。现在它已经运行了2个多小时,没有完成的迹象。我们中止了那个,并在 EXIT WHEN 为 20 时再次尝试它,但它仍然没有显示实际完成的迹象。

我们通过 SQLDeveloper 17.3 运行这些,每个 (4) 表有大约 15k 行。

我们的目标是获取我们数据库中的所有 SSN,并将第一个字符更改为非法字符,将最后两个字符更改为随机的 A-Z 组合。然后,我们必须在使用它的每个表中更新该 SSN (4)。

declare
    v_random varchar2(2); 
    v_origin_ssn varchar2(100);
    v_working_start varchar2(100);
    v_working_middle varchar2(100);
    v_new_ssn varchar2(100);
begin

    for o in (
        select distinct ssn  --loop all rows in tbl_customer
        from program_one.tbl_customer
    )
    loop
        if regexp_like(o.ssn, '^[A-Za-z9].*[A-Z]$') then continue; --if this is already scrambled, skip
        else 
            select dbms_random.string('U', 2) --create random 2 cap letters
                into v_random
            from dual;

            v_origin_ssn := o.ssn; --set origin ssn with the existing ssn

            if regexp_like(o.ssn, '^[A-Za-z]') --if first char is already alpha, leave it alone, otherwise 9
                then v_working_start := substr(o.ssn, 1, 1);
                else v_working_start := 9; 
            end if;

            v_working_middle := substr(o.ssn, 2, 6); --set middle ssn with the unchanged numbers

            v_new_ssn := v_working_start||v_working_middle||v_random; --create new sanitized ssn

            update program_one.tbl_customer --update if exists in tbl_customer
            set ssn = v_new_ssn
            where ssn = v_origin_ssn;

            commit;

            update program_one.tbl_mhc_backup --update if exists ssn tbl_mhc_backup
            set ssn = v_new_ssn
            where ssn = v_origin_ssn;

            commit;

            update program_two.tbl_waiver --update if exists ssn tbl_waiver
            set ssn = v_new_ssn
            where ssn = v_origin_ssn;

            commit;

            update program_two.tbl_pers --update if exists in tbl_pers
            set ssan = v_new_ssn
            where ssan = v_origin_ssn;

            commit;

        end if;

        --dbms_output.put_line(v_origin_ssn||' : '||v_new_ssn); --output test string to verify working correctly

    end loop;

end;

【问题讨论】:

三个是您进行逐行更新的原因吗?还是经常犯?更新是否会被另一个会话中未提交的更改阻止? 执行期间会话在等待什么? 如果我是你,我会创建一个新表来保存旧值和新值(你应该能够通过单个 insert into ... select ... 语句填充),然后你可以使用使用更新或(更有可能)合并语句来更新其他表中的值。批量执行应该比您的逐行(又名逐个慢)方法快得多。 @alexpoole 我们运行它的开发服务器在我们的测试期间不可用,因此不应该有任何竞争性更改。当我们今天离开时,我们让它每 20 次循环而不是每次更新后提交。 @bonist:我支持你的建议并写了一些示例代码。 Daminta:为了您的站立:建议先在真人大小的副本上进行测试运行以获得时间。我相信它会非常快。也许在单独的模式中进行,也许涉及 DBA。无论如何,这是正确的、行业标准的做法。 【参考方案1】:

我会在没有纯 SQL 的函数的情况下这样做:

用新旧 ssn 创建一个表:

CREATE TABLE tmp_ssn AS
  SELECT ssn, '9'||substr(ssn,2,6)||dbms_random.string('U',2) as new_ssn
    FROM (SELECT distinct ssn FROM program_one.tbl_customer);
CREATE UNIQUE INDEX ui_tmp_ssn ON tmp_ssn(ssn, new_ssn);
EXEC DBMS_STATS.GATHER_TABLE_STATS(null,'tmp_ssn');

...然后一一更新表格:

MERGE INTO program_one.tbl_customer z USING tmp_ssn q ON (z.ssn=q.ssn)
 WHEN MATCHED THEN UPDATE z.ssn = q.new_ssn;
COMMIT;

MERGE INTO program_one.tbl_mhc_backup z USING tmp_ssn q ON (z.ssn=q.ssn)
 WHEN MATCHED THEN UPDATE z.ssn = q.new_ssn;
COMMIT;

etc

如果这仍然很慢,我会这样做

RENAME tbl_customer to tbl_customer_old;
CREATE TABLE tbl_customer as
  SELECT s.new_ssn as ssn, t.col1, t.col2, ... , t.coln
    FROM tbl_customer_old t JOIN tmp_ssn s USING(ssn);
DROP TABLE tbl_customer_old;

【讨论】:

到目前为止,这基本上是我们使用的方法。在我们最慢的开发服务器上,每张桌子运行大约 180 秒。我们仍然将它作为一个函数运行,但是创建临时表以首先插入然后运行合并非常快。我会等到明天再接受答案。

以上是关于PL/SQL 加速长函数的主要内容,如果未能解决你的问题,请参考以下文章

去掉 PL/SQL 函数末尾的逗号 [Oracle PL/SQL]

PL/SQL编程(函数包变量)

PL/SQL编程_概述

PL/SQL自定义函数

PL/Sql 函数

PL/SQL 函数参数