构建可扩展的应用

设计您的应用以使用相同的资源,而不考虑用户数量和数据量,并且不会使系统资源过载。

关于可扩展应用程序

可伸缩应用程序可以处理较大的工作量,同时系统资源使用量也会相应增加。

可伸缩应用程序可以处理较大的工作量,同时系统资源使用量也会相应增加。例如,如果工作量增加一倍,则可伸缩应用程序使用的系统资源数量是其两倍。

不可扩展应用程序耗尽系统资源;因此,如果增加应用程序工作量,则无法再增加吞吐量。不可伸缩的应用程序会导致固定的吞吐量和较差的响应时间。

资源耗尽的示例包括:

设计您的应用以使用相同的资源,而不考虑用户数量和数据量,并且不会使系统资源过载。

使用绑定变量提高可扩展性

正确使用绑定变量,可以开发高效、可扩展的应用程序。

绑定变量是 SQL 语句中的占位符,必须将其替换为语句成功执行的有效值或值地址。通过使用绑定变量,可以编写 SQL 语句,在运行时接受输入或参数。

正如子程序可以有参数,其值由调用程序提供一样,SQL 语句可以有绑定变量占位符,其值(称为绑定变量)在运行时提供。正如子程序被编译一次,然后多次使用不同的参数运行一样,具有绑定变量占位符的 SQL 语句会硬性解析一次,然后使用不同的绑定变量进行软性语法分析。

硬性语法分析(包括优化和行源生成)是 CPU 密集型操作。soft parse 跳过优化和行源生成并直接执行,通常比对同一语句进行硬性语法分析快得多。(有关 SQL 处理的概述(包括硬语法分析与软语法分析之间的区别),请参阅 Oracle Database Concepts。)

硬性语法分析不仅是 CPU 密集型操作,而且是无法扩展的操作,因为它无法与许多其他操作同时执行。有关并发性和可扩展性的更多信息,请参阅关于并发性和可扩展性

Example 8-1 显示了没有绑定变量的查询与具有绑定变量的语义等效查询之间的性能差异。前者速度较慢,使用更多闩锁(有关闩锁如何影响可扩展性的信息,请参见)。为了收集和显示性能统计信息,该示例使用 Runstats 工具,如“ Comparing Programming Techniques with Runstats ”中所述。

注:

注:

虽然软性语法分析比硬性语法分析更有效,但多次软性语法分析语句的成本仍然很高。要最大限度地提高应用的效率和可扩展性,请尽量减少语法分析。最小化语法分析的最简单方法是使用 PL/SQL。

示例 8-1 绑定变量可提高性能

CREATE TABLE t ( x VARCHAR2(5) );

DECLARE
  TYPE rc IS REF CURSOR;
  l_cursor rc;
BEGIN
  runstats_pkg.rs_start;  -- Collect statistics for query without bind variable

  FOR i IN 1 .. 5000 LOOP
    OPEN l_cursor FOR 'SELECT x FROM t WHERE x = ' || TO_CHAR(i);
    CLOSE l_cursor;
  END LOOP;

  runstats_pkg.rs_middle;  -- Collect statistics for query with bind variable

  FOR i IN 1 .. 5000 LOOP
    OPEN l_cursor FOR 'SELECT x FROM t WHERE x = :x' USING i;
    CLOSE l_cursor;
  END LOOP;

  runstats_pkg.rs_stop(500);  -- Stop collecting statistics
end;
/

结果与以下文本类似:

Run 1 ran in 740 hsec
Run 2 ran in 30 hsec
Run 1 ran in 
2466.67% of the time of run 2

Name                                   Run 1         Run 2    Difference
STAT...recursive cpu usage               729            19          -710
STAT...CPU used by this sessio           742            30          -712
STAT...parse time elapsed              1,051             4        -1,047
STAT...parse time cpu                  1,066             2        -1,064
STAT...session cursor cache hi             1         4,998         4,997
STAT...table scans (short tabl         5,000             1        -4,999
STAT...parse count (total)            10,003         5,004        -4,999
LATCH.session idle bit                 5,003             3        -5,000
LATCH.session allocation               5,003             3        -5,000
STAT...execute count                  10,003         5,003        -5,000
STAT...opened cursors cumulati        10,003         5,003        -5,000
STAT...parse count (hard)             10,001             5        -9,996
STAT...CCursor + sql area evic        10,000             1        -9,999
STAT...enqueue releases               10,008             7       -10,001
STAT...enqueue requests               10,009             7       -10,002
STAT...calls to get snapshot s        20,005         5,006       -14,999
STAT...calls to kcmgcs                20,028            35       -19,993
STAT...consistent gets pin (fa        20,013            17       -19,996
LATCH.call allocation                 20,002             6       -19,996
STAT...consistent gets from ca        20,014            18       -19,996
STAT...consistent gets                20,014            18       -19,996
STAT...consistent gets pin            20,013            17       -19,996
LATCH.simulator hash latch            20,014            11       -20,003
STAT...session logical reads          20,080            75       -20,005
LATCH.shared pool simulator           20,046             5       -20,041
LATCH.enqueue hash chains             20,343            15       -20,328
STAT...recursive calls                40,015        15,018       -24,997
LATCH.cache buffers chains            40,480           294       -40,186
STAT...session pga memory max        131,072        65,536       -65,536
STAT...session pga memory            131,072        65,536       -65,536
LATCH.row cache objects              165,209           139      -165,070
STAT...session uga memory max        219,000             0      -219,000
LATCH.shared pool                    265,108           152      -264,956
STAT...logical read bytes from   164,495,360       614,400  -163,880,960

Run 1 latches total compared to run 2 -- difference and percentage
         Run 1         Run 2          Diff       Pct
       562,092           864      -561,228  2,466.67%

PL/SQL procedure successfully completed.

使用 PL/SQL 提高可扩展性

某些 PL/SQL 功能可以帮助您提高应用的可扩展性。

PL/SQL 如何尽可能减少语法分析

针对数据库访问进行了优化的 PL/SQL 可无提示地缓存语句。在 PL/SQL 中,当关闭游标时,游标会从您的角度关闭,也就是说,在需要打开游标的位置无法使用游标,但是 PL/SQL 实际上会使游标保持打开状态并缓存其语句。

如果再次使用高速缓存的语句,PL/SQL 将使用相同的游标,从而避免进行语法分析。(如有必要,PL/SQL 会关闭缓存的语句,例如,如果您的程序必须打开另一个游标,但这样做将超过 OPEN_CURSORS 的 init.ora 设置。)

PL/SQL 只能无提示地高速缓存运行时无法更改的 SQL 语句。

关于 EXECUTE IMMEDIATE 语句

EXECUTE IMMEDIATE 语句可以在单个操作中构建并运行动态 SQL 语句。

EXECUTE IMMEDIATE 语句的基本语法如下:

EXECUTE IMMEDIATE sql_statement

sql_statement 是表示 SQL 语句的字符串。如果每次运行 EXECUTE IMMEDIATE 语句时 sql_statement 具有相同的值,则 PL/SQL 可以高速缓存 EXECUTE IMMEDIATE 语句。如果每次运行 EXECUTE IMMEDIATE 语句时 sql_statement 可以不同,则 PL/SQL 无法高速缓存 EXECUTE IMMEDIATE 语句。

另请参见:

关于 OPEN FOR 语句

OPEN FOR 语句具有以下基本语法。

OPEN FOR 语句的基本语法为:

OPEN cursor_variable FOR query

您的应用程序可以在关闭 cursor_variable 之前为其打开多个不同的查询。由于 PL/SQL 在运行时才能确定不同查询的数量,因此 PL/SQL 无法高速缓存 OPEN FOR 语句。

如果不需要使用游标变量,则使用声明的游标,以提高编程的性能和易用性。有关详细信息,请参阅 Oracle Database Development Guide

另请参见:

关于 DBMS_SQL 程序包

DBMS_SQL 程序包是用于构建、运行和描述动态 SQL 语句的 API。如果 PL/SQL 编译器无法在编译时确定输出主机变量的数量或类型(选择列表项)或输入绑定变量,则必须使用 DBMS_SQL 程序包而不是 EXECUTE IMMEDIATE 语句。

DBMS_SQL 程序包是用于构建、运行和描述动态 SQL 语句的 API。与使用 EXECUTE IMMEDIATE 语句相比,使用 DBMS_SQL 程序包需要更多努力,但是,如果 PL/SQL 编译器在编译时无法确定输出主机变量(选择列表项)或输入绑定变量的数量或类型,则必须使用 DBMS_SQL 程序包。

另请参见:

关于批量 SQL

批量 SQL 可减少 PL/SQL 与 SQL 之间的“往返”次数,从而减少资源使用。

如果不使用批量 SQL,则一次从数据库 (SQL) 检索一行,然后对其进行处理 (PL/SQL),然后将其返回到数据库 (SQL)。使用批量 SQL,可以从数据库中检索一组行,处理一组行,然后将整个集合返回到数据库中。

Oracle 建议在从数据库检索多行时使用成批 SQL,将它们返回到数据库,如示例 8-2 中所示。如果检索多个行但未返回这些行,则不需要批量 SQL;例如:

FOR x IN (SELECT * FROM t WHERE ... )  -- Retrieve row set (implicit array fetch)
  LOOP
    DBMS_OUTPUT.PUT_LINE(t.x);          -- Process rows but do not return them
  END LOOP;

Example 8-2 通过具有列 object_name 的表 t 循环,检索 100 行集,处理这些行并将其返回到数据库中。(将批量 FETCH 语句限制为 100 行需要显式游标。)

Example 8-3 执行与 Example 8-2 相同的作业,而不执行批量 SQL。

Example 8-2Example 8-3 的 TKPROF 报告所示,使用此作业的批量 SQL 将 CPU 时间缩短近 50%:

SELECT ROWID RID, OBJECT_NAME FROM T T_BULK

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      721      0.17       0.17          0      22582          0       71825
********************************************************************************
UPDATE T SET OBJECT_NAME = :B1 WHERE ROWID = :B2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    719     12.83      13.77          0      71853      74185       71825
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      720     12.83      13.77          0      71853      74185       71825


SELECT ROWID RID, OBJECT_NAME FROM T T_SLOW_BY_SLOW

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      721      0.17       0.17          0      22582          0       71825
********************************************************************************
UPDATE T SET OBJECT_NAME = :B2 WHERE ROWID = :B1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  71824     21.25      22.25          0      71836      73950       71824
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    71825     21.25      22.25          0      71836      73950       71824

但是,对于此作业使用批量 SQL 使用的 CPU 时间和代码比使用单个 SQL 语句更多,因为此 TKPROF 报告显示:

UPDATE T SET OBJECT_NAME = SUBSTR(OBJECT_NAME,2) || SUBSTR(OBJECT_NAME,1,1)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      1.30       1.44          0       2166      75736       71825
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      1.30       1.44          0       2166      75736       71825

示例 8-2 批量 SQL

CREATE OR REPLACE PROCEDURE bulk AS
  TYPE ridArray IS TABLE OF ROWID;
  TYPE onameArray IS TABLE OF t.object_name%TYPE;

  CURSOR c is SELECT ROWID rid, object_name  -- explicit cursor
              FROM t t_bulk;

  l_rids    ridArray;
  l_onames  onameArray;
  N         NUMBER := 100;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT
    INTO l_rids, l_onames LIMIT N;   -- retrieve N rows from t

    FOR i in 1 .. l_rids.COUNT
      LOOP                           -- process N rows
        l_onames(i) := substr(l_onames(i),2) || substr(l_onames(i),1,1);
      END LOOP;

      FORALL i in 1 .. l_rids.count  -- return processed rows to t
        UPDATE t
        SET object_name = l_onames(i)
        WHERE ROWID = l_rids(i);
        EXIT WHEN c%NOTFOUND;
  END LOOP;
  CLOSE c;
END;
/

示例 8-3 - 无批量 SQL

CREATE OR REPLACE PROCEDURE slow_by_slow AS
BEGIN
  FOR x IN (SELECT rowid rid, object_name FROM t t_slow_by_slow)
    LOOP
      x.object_name := substr(x.object_name,2) || substr(x.object_name,1,1);

      UPDATE t
      SET object_name = x.object_name
      WHERE rowid = x.rid;
    END LOOP;
END;

另请参见:

关于并发性和可扩展性

并发是同时执行多个事务处理。您的应用处理并发性越好,可扩展性就越高。可伸缩应用程序可以处理较大的工作量,同时系统资源使用量也会相应增加。

并发是同时执行多个事务处理。并发事务处理中的对账单可以更新相同的数据。您的应用处理并发性越好,可扩展性就越高。可伸缩应用程序可以处理较大的工作量,同时系统资源使用量也会相应增加。例如,如果工作量增加一倍,则可伸缩应用程序使用的系统资源数量是其两倍。

并发事务处理必须产生有意义且一致的结果。因此,多用户数据库必须提供以下内容:

Oracle Database 使用多版本一致性模型以及各种类型的锁和事务隔离级别来保持数据一致性。有关 Oracle Database 锁定机制的概述,请参阅 Oracle Database Concepts。有关 Oracle Database 事务处理隔离级别的概览,请参阅 Oracle Database Concepts

为了描述同时运行事务时的一致事务行为,数据库研究人员定义了一个名为 serializable 的事务隔离类别。串行化事务处理在显示为单用户数据库的环境中运行。在特定情况下,可串行化事务是可取的,但对于 99% 的工作负载,读取提交的隔离是完美的。

Oracle Database 具有可提高并发性和可扩展性的功能,例如序列、闩锁、非阻塞读取和写入以及共享 SQL。

另请参阅:Oracle Database Concepts,了解有关数据并发性和一致性的更多信息

关于序列和并发

序列可消除序列化,从而提高应用的并发性和可扩展性。

序列是多个用户可以从中生成唯一整数的方案对象,当您需要唯一主键时,该方案非常有用。

如果没有序列,必须以编程方式生成唯一主键值。用户通过选择最近生成的值并将其递增来获取新的主键值。此技术要求在事务处理期间锁定,并导致多个用户等待下一个主键值,即事务处理序列化。序列可消除序列化,从而提高应用的并发性和可扩展性。

另请参见:

关于闩锁和并发

闩锁的增加意味着更多基于并发的等待,因此可伸缩性降低。

闩锁是一种简单的低级串行化机制,可协调多用户对共享数据结构的访问。闩锁可保护共享内存资源免受多个进程访问时的损坏。

闩锁的增加意味着更多基于并发的等待,因此可伸缩性降低。如果可以使用在开发期间运行速度稍快的方法或使用较少闩锁的方法,请使用后者。

另请参见:

关于非阻塞读写和并发

在 Oracle Database 中,非阻塞读取和写入允许查询与正在读取的数据的更改同时执行,而不会阻塞或停止。非阻塞读取和写入允许一个会话读取数据,而另一个会话正在更改该数据。

关于共享 SQL 和并发

Oracle Database 将 SQL 语句编译为可执行对象一次,然后其他会话可以重用该对象(只要该对象存在)。这种名为 shared SQL 的 Oracle Database 功能允许数据库仅执行一次资源密集型操作编译和优化 SQL 语句,而不是每次会话使用同一 SQL 语句时。

另请参阅:Oracle Database Concepts,了解有关共享 SQL 的更多信息

限制并发会话数

您拥有的并发会话越多,基于并发的等待就越多,响应时间越慢。

如果您的计算机具有 n 个 CPU 核心,则至多 n 个会话可以同时处于活动状态。每个额外的“并发”会话必须等待 CPU 核心可用,然后才能变为活动状态。如果某些等待会话仅等待 I/O,则将并发会话数增加到稍微大于 n 可能会稍微提高运行时性能。但是,过多增加并发会话数会显著降低运行时性能。

SESSIONS 初始化参数确定系统中并发用户的最大数量。有关详细信息,请参阅 Oracle Database Reference

另请参见:http://www.youtube.com/watch?v=xNDnVOCdvQ0 ,了解如何将具有 12 个 CPU 核心的计算机上的并发会话数从数千个减少到 96 个

比较编程技术与 Runstats

使用 Runstats 工具,您可以比较两种编程技术的性能,以查看哪一种更好。

关于 Runstats

使用 Runstats 工具,您可以比较两种编程技术的性能,以查看哪一种更好。

Runstats 度量以下值:

在前面的测量中,最重要的是锁定(请参见 About Latches and Concurrency )。

另请参阅:使用 Runstats 的 Example 8-1

设置运行状态

Runstats 工具作为使用视图和临时表的程序包实现。

注:对于以下过程的步骤 1 ,您需要对动态性能视图 V$STATNAME、V$MYSTAT 和 V$LATCH 具有 SELECT 权限。如果您无法获得此权限,请让具有该权限的人在步骤 1 中创建该视图,并向您授予对该视图的 SELECT 权限。

设置 Runstats 工具的步骤:

  1. 创建运行状态使用的视图:

     CREATE OR REPLACE VIEW stats
     AS SELECT 'STAT...' || a.name name, b.value
     FROM V$STATNAME a, V$MYSTAT b
     WHERE a.statistic# = b.statistic#
     UNION ALL
     SELECT 'LATCH.' || name, gets
     FROM V$LATCH;
    
  2. 创建 Runstats 使用的临时表:

     DROP TABLE run_stats;
    
     CREATE GLOBAL TEMPORARY TABLE run_stats
     ( runid VARCHAR2(15),
       name VARCHAR2(80),
       value INT )
     ON COMMIT PRESERVE ROWS;
    
  3. 创建此程序包说明:

     CREATE OR REPLACE PACKAGE runstats_pkg
     AS
       PROCEDURE rs_start;
       PROCEDURE rs_middle;
       PROCEDURE rs_stop( p_difference_threshold IN NUMBER DEFAULT 0 );
     end;
     /
    

    参数 p_difference_threshold 控制 Runstats 显示的统计信息量和锁定数据。只有当两种技术的差异大于 p_difference_threshold 时,运行状态才会显示数据。默认情况下,Runstats 显示所有数据。

  4. 创建以下程序包主体:

     CREATE OR REPLACE PACKAGE BODY runstats_pkg
     AS
       g_start NUMBER;
       g_run1 NUMBER;
       g_run2 NUMBER;
    
       PROCEDURE rs_start
       IS
       BEGIN
         DELETE FROM run_stats;
    
         INSERT INTO run_stats
         SELECT 'before', stats.* FROM stats;
    
         g_start := DBMS_UTILITY.GET_TIME;
       END rs_start;
    
       PROCEDURE rs_middle
       IS
       BEGIN
         g_run1 := (DBMS_UTILITY.GET_TIME - g_start);
    
         INSERT INTO run_stats
         SELECT 'after 1', stats.* FROM stats;
    
         g_start := DBMS_UTILITY.GET_TIME;
       END rs_middle;
    
       PROCEDURE rs_stop( p_difference_threshold IN NUMBER DEFAULT 0 )
       IS
       BEGIN
         g_run2 := (DBMS_UTILITY.GET_TIME - g_start);
    
         DBMS_OUTPUT.PUT_LINE
           ('Run 1 ran in ' || g_run1 || ' hsec');
    
         DBMS_OUTPUT.PUT_LINE
           ('Run 2 ran in ' || g_run2 || ' hsec');
    
         DBMS_OUTPUT.PUT_LINE
           ('Run 1 ran in ' || round(g_run1/g_run2*100, 2) || '% of the time of run 2');
    
         DBMS_OUTPUT.PUT_LINE( CHR(9) );
    
         INSERT INTO run_stats
         SELECT 'after 2', stats.* FROM stats;
    
         DBMS_OUTPUT.PUT_LINE
           ( RPAD( 'Name', 30 ) ||
             LPAD( 'Run 1', 14) ||
    
             LPAD( 'Run 2', 14) ||
    
             LPAD( 'Difference', 14)
           );
    
         FOR x IN
         ( SELECT RPAD( a.name, 30 ) ||
                 TO_CHAR( b.value - a.value, '9,999,999,999' ) ||
                 TO_CHAR( c.value - b.value, '9,999,999,999' ) ||
                 TO_CHAR( ( (c.value - b.value) - (b.value - a.value)),
                   '9,999,999,999' ) data
           FROM run_stats a, run_stats b, run_stats c
           WHERE a.name = b.name
             AND b.name = c.name
             AND a.runid = 'before'
             AND b.runid = 'after 1'
             AND c.runid = 'after 2'
             AND (c.value - a.value) > 0
             AND abs((c.value - b.value) - (b.value - a.value)) >
               p_difference_threshold
         ORDER BY ABS((c.value - b.value) - (b.value - a.value))
         ) LOOP
             DBMS_OUTPUT.PUT_LINE( x.data );
         END LOOP;
    
         DBMS_OUTPUT.PUT_LINE( CHR(9) );
    
         DBMS_OUTPUT.PUT_LINE(
           'Run 1 latches total compared to run 2 -- difference and percentage' );
    
         DBMS_OUTPUT.PUT_LINE
           ( LPAD( 'Run 1', 14) ||
    
             LPAD( 'Run 2', 14) ||
    
             LPAD( 'Diff', 14) ||
    
             LPAD( 'Pct', 10)
           );
    
         FOR x IN
         ( SELECT TO_CHAR( run1, '9,999,999,999' ) ||
                 TO_CHAR( run2, '9,999,999,999' ) ||
                 TO_CHAR( diff, '9,999,999,999' ) ||
                 TO_CHAR( ROUND( g_run1/g_run2*100, 2), '99,999.99' ) || '%' data
           FROM ( SELECT SUM (b.value - a.value) run1,
                         SUM (c.value - b.value) run2,
                         SUM ( (c.value - b.value) - (b.value - a.value)) diff
                 FROM run_stats a, run_stats b, run_stats c
                 WHERE a.name = b.name
                   AND b.name = c.name
                   AND a.runid = 'before'
                   AND b.runid = 'after 1'
                   AND c.runid = 'after 2'
                   AND a.name like 'LATCH%'
               )
         ) LOOP
             DBMS_OUTPUT.PUT_LINE( x.data );
         END LOOP;
    
       END rs_stop;
    
     END;
     /
    

另请参见:

使用 Runstats

本主题提供了使用 Runstats 工具的语法。

要使用 Runstats 来比较两种编程技术,请使用以下语法从匿名块调用 runstats_pkg 过程:

[ DECLARE local_declarations ]
BEGIN
  runstats_pkg.rs_start;
  code_for_first_technique
  runstats_pkg.rs_middle;
  code_for_second_technique
  runstats_pkg.rs_stop(n);
END;
/

另请参阅:使用 Runstats 的 Example 8-1

现实世界中的性能和数据处理技术

在数据仓库环境中,数据库应用程序中的一项常见任务是查询或修改庞大的数据集。应用程序开发人员面临的问题是如何在处理大型数据集时实现高性能。

处理技术分为两类:迭代和基于集合。经过多年的测试,RealWorld Performance 团队发现,对于处理大型数据集的数据库应用,基于集合的处理技术可以更好地执行多个数量级

本主题包括以下主要子主题:

关于迭代数据处理

在迭代处理中,应用程序使用条件逻辑循环处理一组行。

通常,尽管不一定,迭代处理使用客户机/服务器模型,如下所示:

  1. 将一组行从数据库服务器传输到客户机应用程序。

  2. 在客户机应用程序中处理组。

  3. 将已处理的组传输回数据库服务器。

您可以使用三种主要技术实现迭代算法:逐行处理、数组处理和手动并行。

迭代处理:逐行处理

在逐行处理中,单个进程会循环处理数据集,并一次对单个行进行操作。在典型实施中,应用程序从数据库中检索每一行,在中间层对其进行处理,然后将该行发送回数据库,该数据库执行 DML 并提交。

假设您的功能要求是查询一个名为 ext_scan_events 的外部表,然后将其行插入到名为 stage1_scan_events 的按堆组织的中转表中。以下 PL/SQL 块使用逐行技术来满足此要求:

declare
  cursor c is select s.* from ext_scan_events s;
  r c%rowtype;
begin
  open c;
  loop
    fetch c into r;
    exit when c%notfound;
    insert into stage1_scan_events d values r;
    commit;
  end loop;
  close c;
end;

逐行方法具有以下优点:

逐行方法具有以下缺点:

另请参见:RWP #7 Set-Based Processing

迭代处理:数组

数组处理与逐行处理相同,只是处理每个迭代中的一组行,而不是单个行。

假设您的功能要求与示例 X-X 中相同:查询一个名为 ext_scan_events 的外部表,然后将其行插入到名为 stage1_scan_events 的按堆组织的分段表中。以下 PL/SQL 块使用数组技术来满足此要求:

declare
  cursor c is select s.* from ext_scan_events s;
  type t is table of c%rowtype index by binary_integer;
  a t;
  rows binary_integer := 0;
begin
  open c;
  loop
    fetch c bulk collect into a limit array_size;
    exit when a.count = 0;
    forall i in 1..a.count
      insert into stage1_scan_events d values a(i);
    commit;
  end loop;
  close c;
end;

在 FETCH STATEMENT 中使用 BULK COLLECT 运算符时,前面的代码与等效的逐行代码不同,后者受 PLS_INTEGER 类型的 array_size 值的限制。例如,如果 array_size 设置为 100,则应用程序将按 100 组提取行。

与逐行技术相比,数组技术具有以下优点:

此技术的缺点与逐行处理相同。对于大型数据集,处理时间可能是不可接受的。此外,应用程序必须在单个 CPU 核心上串行运行,因此无法利用 Oracle Database 的本机并行性。

迭代处理:手动并行处理

手动并行使用与逐行处理和数组处理相同的迭代算法,但使多个服务器进程可以划分工作并并行运行。

假设功能要求与逐行和数组示例中的要求相同。主要差异如下:

以下 PL/SQL 代码使用手动并行处理:

declare
  sqlstmt varchar2(1024) := q'[
-- BEGIN embedded anonymous block
  cursor c is select s.* from ext_scan_events_${thr} s;
  type t is table of c%rowtype index by binary_integer;
  a t;
  rows binary_integer := 0;
begin
  for r in (select ext_file_name from ext_scan_events_dets where ora_hash(file_seq_nbr,${thrs}) = ${thr})
  loop
    execute immediate
      'alter table ext_scan_events_${thr} location' || '(' || r.ext_file_name || ')';
    open c;
    loop
      fetch c bulk collect into a limit ${array_size};
      exit when a.count = 0;
      forall i in 1..a.count
        insert into stage1_scan_events d values a(i);
      commit;
--  demo instrumentation
      rows := rows + a.count; if rows > 1e3 then exit when not sd_control.p_progress('loading','userdefined',rows); rows := 0; end if;
    end loop;
    close c;
  end loop;
end;
-- END   embedded anonymous block
]';

begin
  sqlstmt := replace(sqlstmt, '${array_size}', to_char(array_size));
  sqlstmt := replace(sqlstmt, '${thr}', thr);
  sqlstmt := replace(sqlstmt, '${thrs}', thrs);
  execute immediate sqlstmt;
end;

ORA_HASH 函数将 ext_scan_events_dets 表分成 32 个均匀分布的存储桶,然后 SELECT 语句检索存储桶 0 的文件名。对于存储桶中的每个文件名,程序会将外部表的位置设置为此文件名。然后,程序使用批处理查询外部表,插入中转表,然后提交。

作业 1 正在执行时,其他 31 个 Oracle Scheduler 作业将并行执行。这样,每个作业将同时读取扫描事件文件的不同子集,并将记录从其子集插入到同一中转表中。

与替代迭代技术相比,手动并行技术具有以下优点:

手动并行技术具有以下缺点:

另请参阅:RWP #8:基于集合的并行处理

关于基于集合的处理

基于集合的处理是一种 SQL 技术,用于处理数据库内的数据集。

在基于集合的模型中,SQL 语句定义结果,并允许数据库确定最有效的方法来获取结果。相反,迭代算法使用条件逻辑将数据库中的每一行或每一组行从客户机应用程序中提取到客户机应用程序中,处理客户机上的数据,然后将数据发送回数据库。基于集的处理可消除网络往返和数据库 API 开销,因为数据永远不会离开数据库。

承担与前面的示例相同的功能要求。以下 SQL 语句使用基于集的算法满足此要求:

alter session enable parallel dml;
insert /*+ APPEND */ into stage1_scan_events d
  select s.* from ext_scan_events s;
commit;

由于 INSERT 语句包含 ext_scan_events 表的子查询,因此 single SQL 语句会读取和写入所有行。此外,在数据库插入所有行之后,应用程序会执行单个 COMMIT。相反,迭代应用程序在插入每行或每组行后执行 COMMIT。

与迭代技术相比,基于集合的技术具有显著的优势:

基于集合的处理确实有一些潜在的缺点:

另请参见:RWP #7 Set-Based Processing、RWP #8:Set-Based Parallel Processing、RWP #9:Set-Based Processing – Data Deduplication、RWP #10:Set-Based Processing – Data Transformations 和 RWP #11:Set-Based Processing – Data Aggregation。