Creación de aplicaciones ampliables

Diseñe las aplicaciones para que utilicen los mismos recursos, independientemente de los grupos de usuarios y los volúmenes de datos, y no para sobrecargar los recursos del sistema.

Acerca de las aplicaciones escalables

Una aplicación escalable puede procesar una carga de trabajo más grande con un aumento proporcional en el uso de recursos del sistema.

Una aplicación escalable puede procesar una carga de trabajo más grande con un aumento proporcional en el uso de recursos del sistema. Por ejemplo, si duplica su carga de trabajo, una aplicación escalable utiliza el doble de recursos del sistema.

Una aplicación no escalable agota un recurso del sistema; por lo tanto, si aumenta la carga de trabajo de la aplicación, no se puede obtener más rendimiento. Las aplicaciones no escalables generan un rendimiento fijo y tiempos de respuesta deficientes.

Ejemplos de agotamiento de recursos son:

Diseñe las aplicaciones para que utilicen los mismos recursos, independientemente de los grupos de usuarios y los volúmenes de datos, y no para sobrecargar los recursos del sistema.

Uso de Variables de Enlace para Mejorar la Escalabilidad

Las variables de enlace, utilizadas correctamente, le permiten desarrollar aplicaciones eficientes y escalables.

Una variable de enlace es un marcador de posición en una sentencia SQL que se debe sustituir por un valor o una dirección de valor válidos para que la sentencia se ejecute correctamente. Mediante variables ligadas, puede escribir una sentencia SQL que acepte entradas o parámetros en tiempo de ejecución.

Del mismo modo que un subprograma puede tener parámetros, cuyos valores suministra el invocador, una sentencia SQL puede tener marcadores de posición de variable de enlace, cuyos valores (denominados variables de enlace) se proporcionan en tiempo de ejecución. Al igual que un subprograma se compila una vez y, a continuación, se ejecuta muchas veces con diferentes parámetros, una sentencia SQL con marcadores de posición de variable de enlace se analiza una vez y, a continuación, se analiza de forma flexible con diferentes variables de enlace.

Un análisis pesado, que incluye optimización y generación de origen de fila, es una operación que consume mucha CPU. Un análisis suave, que omite la optimización y la generación del origen de fila y continúa directamente hasta la ejecución, suele ser mucho más rápido que un análisis pesado de la misma sentencia. Para obtener una visión general del procesamiento SQL, que incluye la diferencia entre un análisis pesado y ligero, consulte Conceptos de Oracle Database.

No solo es una operación de análisis pesado que utiliza mucha CPU, sino que es una operación no escalable, ya que no se puede realizar simultáneamente con muchas otras operaciones. Para obtener más información sobre la simultaneidad y la escalabilidad, consulte "Acerca de la simultaneidad y la escalabilidad".

El Ejemplo 8-1 muestra la diferencia de rendimiento entre una consulta sin una variable de enlace y una consulta semánticamente equivalente con una variable de enlace. El primero es más lento y utiliza muchos más bloqueos internos (para obtener información sobre cómo afectan los bloqueos internos a la escalabilidad, consulte). Para recopilar y mostrar estadísticas de rendimiento, en el ejemplo se utiliza la herramienta Runstats, que se describe en "Comparing Programming Techniques with Runstats".

Nota:

Nota:

Aunque el análisis ligero es más eficaz que el análisis pesado, el costo del análisis ligero de una sentencia muchas veces sigue siendo muy alto. Para maximizar la eficiencia y la escalabilidad de su aplicación, minimice el análisis. La forma más sencilla de minimizar el análisis es utilizar PL/SQL.

Ejemplo 8-1 La variable de enlace mejora el rendimiento

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;
/

El resultado es similar al siguiente texto:

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.

Uso de PL/SQL para Mejorar la Escalabilidad

Ciertas funciones de PL/SQL pueden ayudarle a mejorar la escalabilidad de las aplicaciones.

Cómo PL/SQL Minimiza el Análisis

PL/SQL, que está optimizado para el acceso a la base de datos, almacena en caché sentencias de forma silenciosa. En PL/SQL, al cerrar un cursor, el cursor se cierra desde la perspectiva, es decir, no puede utilizarlo cuando se necesita un cursor abierto, pero PL/SQL mantiene el cursor abierto y almacena en caché su sentencia.

Si vuelve a utilizar la sentencia almacenada en caché, PL/SQL utiliza el mismo cursor, evitando así un análisis. (PL/SQL cierra las sentencias almacenadas en caché si es necesario; por ejemplo, si el programa debe abrir otro cursor, pero si lo hace, se superaría el valor init.ora de OPEN_CURSORS).

PL/SQL sólo puede almacenar en caché silenciosamente sentencias SQL que no pueden cambiar en tiempo de ejecución.

Acerca de la sentencia EXECUTE IMMEDIATE

La sentencia EXECUTE IMMEDIATE crea y ejecuta una sentencia SQL dinámica en una sola operación.

La sintaxis básica de la sentencia EXECUTE IMMEDIATE es:

EXECUTE IMMEDIATE sql_statement

sql_statement es una cadena que representa una sentencia SQL. Si la sentencia sql_statement tiene el mismo valor cada vez que se ejecuta la sentencia EXECUTE IMMEDIATE, PL/SQL puede almacenar en caché la sentencia EXECUTE IMMEDIATE. Si la sentencia sql_statement puede ser diferente cada vez que se ejecuta la sentencia EXECUTE IMMEDIATE, PL/SQL no puede almacenar en caché la sentencia EXECUTE IMMEDIATE.

Consulte además:

Acerca de OPEN FOR Statements

La sentencia OPEN FOR tiene la siguiente sintaxis básica.

La sintaxis básica de la sentencia OPEN FOR es:

OPEN cursor_variable FOR query

La aplicación puede abrir cursor_variable para varias consultas diferentes antes de cerrarla. Puesto que PL/SQL no puede determinar el número de consultas diferentes hasta el tiempo de ejecución, PL/SQL no puede almacenar en caché la sentencia OPEN FOR.

Si no necesita utilizar una variable de cursor, utilice un cursor declarado, tanto para un mejor rendimiento como para facilitar la programación. Para obtener más información, consulte Oracle Database Development Guide.

Consulte además:

Acerca del Paquete DBMS_SQL

El paquete DBMS_SQL es una API para crear, ejecutar y describir sentencias SQL dinámicas. Debe utilizar el paquete DBMS_SQL en lugar de la sentencia EXECUTE IMMEDIATE si el compilador PL/SQL no puede determinar en tiempo de compilación el número o los tipos de variables de host de salida (seleccionar elementos de lista) o variables de enlace de entrada.

El paquete DBMS_SQL es una API para crear, ejecutar y describir sentencias SQL dinámicas. El uso del paquete DBMS_SQL requiere más esfuerzo que el uso de la sentencia EXECUTE IMMEDIATE, pero debe utilizar el paquete DBMS_SQL si el compilador PL/SQL no puede determinar en tiempo de compilación el número o los tipos de variables de host de salida (seleccionar elementos de lista) o variables de enlace de entrada.

Consulte además:

Acerca de SQL en Bloque

El SQL en bloque reduce el número de "viajes de ida y vuelta" entre PL/SQL y SQL, con lo que se utilizan menos recursos.

Sin SQL en bloque, recupera una fila a la vez de la base de datos (SQL), la procesa (PL/SQL) y la devuelve a la base de datos (SQL). Con SQL masivo, recupera un juego de filas de la base de datos, procesa el juego de filas y, a continuación, devuelve todo el juego a la base de datos.

Oracle recomienda utilizar SQL en bloque al recuperar varias filas de la base de datos y devolverlas a la base de datos, como en el Ejemplo 8-2. No necesita SQL en bloque si recupera varias filas pero no las devuelve; por ejemplo:

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;

El ejemplo 8-2 realiza bucles a través de una tabla t con un nombre_objeto de columna, recuperando juegos de 100 filas, procesándolos y devolviéndolos a la base de datos. (Para limitar la sentencia FETCH en bloque a 100 filas se necesita un cursor explícito).

El ejemplo 8-3 realiza el mismo trabajo que el ejemplo 8-2, sin SQL en bloque.

Como se muestra en estos informes TKPROF para el Ejemplo 8-2 y el Ejemplo 8-3, el uso de SQL en bloque para este trabajo utiliza casi un 50 % menos de tiempo de CPU:

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

Sin embargo, el uso de SQL en bloque para este trabajo utiliza más tiempo de CPU y más código que el uso de una sola sentencia SQL, como muestra este informe 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

Ejemplo 8-2 SQL en bloque

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;
/

Ejemplo 8-3 Sin SQL en Bloque

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;

Consulte además:

Acerca de la simultaneidad y escalabilidad

La concurrencia es la ejecución simultánea de varias transacciones. Cuanto mejor maneje la aplicación la simultaneidad, más escalable será. Una aplicación escalable puede procesar una carga de trabajo más grande con un aumento proporcional en el uso de recursos del sistema.

La concurrencia es la ejecución simultánea de varias transacciones. Las sentencias de transacciones simultáneas pueden actualizar los mismos datos. Cuanto mejor maneje la aplicación la simultaneidad, más escalable será. Una aplicación escalable puede procesar una carga de trabajo más grande con un aumento proporcional en el uso de recursos del sistema. Por ejemplo, si duplica su carga de trabajo, una aplicación escalable utiliza el doble de recursos del sistema.

Las transacciones simultáneas deben producir resultados significativos y coherentes. Por lo tanto, una base de datos multiusuario debe proporcionar lo siguiente:

Oracle Database mantiene la consistencia de los datos mediante un modelo de consistencia de varias versiones y varios tipos de bloqueos y niveles de aislamiento de transacciones. Para obtener una visión general del mecanismo de bloqueo de Oracle Database, consulte Conceptos de Oracle Database. Para obtener una visión general de los niveles de aislamiento de transacciones de Oracle Database, consulte Oracle Database Concepts.

Para describir el comportamiento consistente de las transacciones cuando se ejecutan simultáneamente, los investigadores de la base de datos han definido una categoría de aislamiento de transacciones denominada serializable. Una transacción serializable funciona en un entorno que parece ser una base de datos de un solo usuario. Las transacciones serializables son deseables en casos específicos, pero para el 99% de la carga de trabajo, el aislamiento confirmado de lectura es perfecto.

Oracle Database cuenta con funciones que mejoran la simultaneidad y la escalabilidad, por ejemplo, secuencias, bloqueos internos, lecturas y escrituras sin bloqueo y SQL compartido.

Consulte también: Conceptos de Oracle Database para obtener más información sobre la simultaneidad y consistencia de los datos

Acerca de las Secuencias y Simultaneidad

Las secuencias eliminan la serialización, lo que mejora la simultaneidad y la escalabilidad de la aplicación.

Una secuencia es un objeto de esquema a partir del cual varios usuarios pueden generar enteros únicos, lo que resulta muy útil cuando se necesitan claves primarias únicas.

Sin secuencias, los valores de clave primaria únicos se deben producir mediante programación. Un usuario obtiene un nuevo valor de clave primaria seleccionando el valor producido más recientemente e incrementándolo. Esta técnica requiere un bloqueo durante la transacción y hace que varios usuarios esperen al siguiente valor de clave primaria, es decir, las transacciones se serializan. Las secuencias eliminan la serialización, lo que mejora la simultaneidad y la escalabilidad de la aplicación.

Consulte además:

Acerca de los cierres y la simultaneidad

Un aumento en los bloqueos internos significa más esperas basadas en simultaneidad y, por lo tanto, una disminución en la escalabilidad.

Un bloqueo es un mecanismo de serialización simple y de bajo nivel que coordina el acceso de varios usuarios a estructuras de datos compartidas. Los bloqueos internos protegen los recursos de memoria compartida de la corrupción cuando se accede mediante varios procesos.

Un aumento en los bloqueos internos significa más esperas basadas en simultaneidad y, por lo tanto, una disminución en la escalabilidad. Si puede utilizar un enfoque que se ejecute ligeramente más rápido durante el desarrollo o uno que utilice menos bloqueos internos, utilice el último.

Consulte además:

Acerca de las lecturas y escrituras no bloqueantes y la simultaneidad

En Oracle Database, las lecturas y escrituras sin bloqueo permiten que las consultas se ejecuten simultáneamente con los cambios en los datos que están leyendo, sin bloquear ni parar. Las lecturas y escrituras que no bloquean permiten a una sesión leer datos mientras otra sesión cambia esos datos.

Acerca de SQL Compartido y Simultaneidad

Oracle Database compila una sentencia SQL en un objeto ejecutable una vez y, a continuación, otras sesiones pueden reutilizar el objeto durante el tiempo que exista. Esta función de Oracle Database, denominada SQL compartido, permite a la base de datos realizar operaciones de compilación y optimización de sentencias SQL muy intensivas en recursos solo una vez, en lugar de cada vez que una sesión utiliza la misma sentencia SQL.

Consulte también: Conceptos de Oracle Database para obtener más información sobre SQL compartido

Limitación del Número de Sesiones Simultáneas

Cuantas más sesiones simultáneas tenga, más esperas basadas en simultaneidad tendrá y más lento será el tiempo de respuesta.

Si el equipo tiene n núcleos de CPU, como máximo n sesiones pueden estar activas de forma simultánea. Cada sesión "concurrente" adicional debe esperar a que un núcleo de CPU esté disponible antes de que se pueda activar. Si algunas sesiones en espera solo esperan E/S, el aumento del número de sesiones simultáneas a un poco más de n puede mejorar ligeramente el rendimiento en tiempo de ejecución. Sin embargo, el aumento excesivo del número de sesiones simultáneas reducirá significativamente el rendimiento en tiempo de ejecución.

El parámetro de inicialización SESSIONS determina el número máximo de usuarios simultáneos en el sistema. Para obtener más información, consulte Referencia de Oracle Database.

Consulte también: http://www.youtube.com/watch?v=xNDnVOCdvQ0 para ver un vídeo que muestra el efecto de reducir el número de sesiones simultáneas en un equipo con 12 núcleos de CPU de miles a 96.

Comparación de Técnicas de Programación con Runstats

La herramienta Runstats le permite comparar el rendimiento de dos técnicas de programación para ver cuál es mejor.

Acerca de Runstats

La herramienta Runstats le permite comparar el rendimiento de dos técnicas de programación para ver cuál es mejor.

Runstats mide los siguientes valores:

De las mediciones anteriores, lo más importante es el bloqueo interno (consulte "Acerca de los Bloqueos Internos y la Simultaneidad").

Consulte también: Ejemplo 8-1, que utiliza Runstats

Definición de estadísticas de ejecución

La herramienta Runstats se implementa como un paquete que utiliza una vista y una tabla temporal.

Nota: Para el paso 1 del siguiente procedimiento, necesita el privilegio SELECT en las vistas de rendimiento dinámico V$STATNAME, V$MYSTAT y V$LATCH. Si no puede obtener este privilegio, haga que alguien que tenga el privilegio cree la vista en el paso 1 y le otorgue el privilegio SELECT.

Pasos para configurar la herramienta Runstats:

  1. Cree la vista que Runstats utiliza:

     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. Cree la tabla temporal que Runstats utiliza:

     DROP TABLE run_stats;
    
     CREATE GLOBAL TEMPORARY TABLE run_stats
     ( runid VARCHAR2(15),
       name VARCHAR2(80),
       value INT )
     ON COMMIT PRESERVE ROWS;
    
  3. Cree esta especificación del paquete:

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

    El parámetro p_difference_threshold controla la cantidad de estadísticas y datos de bloqueo interno que muestra Runstats. Runstats muestra datos solo cuando la diferencia para las dos técnicas es mayor que p_difference_threshold. Por defecto, Runstats muestra todos los datos.

  4. Cree el siguiente cuerpo del paquete:

     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;
     /
    

Consulte además:

Uso de Runstats

En este tema se proporciona la sintaxis para utilizar la herramienta Runstats.

Para utilizar Runstats para comparar dos técnicas de programación, llame a los procedimientos runstats_pkg desde un bloque anónimo, utilizando esta sintaxis:

[ 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;
/

Consulte también: Ejemplo 8-1, que utiliza Runstats

Técnicas de procesamiento de datos y rendimiento en el mundo real

Una tarea común en las aplicaciones de base de datos en un entorno de almacén de datos es consultar o modificar un gran juego de datos. El problema para los desarrolladores de aplicaciones es cómo lograr un alto rendimiento al procesar grandes juegos de datos.

Las técnicas de procesamiento se dividen en dos categorías: iterativas y basadas en conjuntos. A lo largo de años de pruebas, el grupo Real-World Performance ha descubierto que las técnicas de procesamiento basadas en conjuntos realizan órdenes de magnitud mejores para las aplicaciones de base de datos que procesan grandes conjuntos de datos.

En este tema se incluyen los siguientes subtemas principales:

Acerca del procesamiento de datos iterativos

En el procesamiento iterativo, las aplicaciones utilizan la lógica condicional para realizar bucles en un juego de filas.

Normalmente, aunque no necesariamente, el procesamiento iterativo utiliza un modelo de cliente/servidor de la siguiente manera:

  1. Transferir un grupo de filas del servidor de base de datos a la aplicación cliente.

  2. Procese el grupo dentro de la aplicación cliente.

  3. Vuelva a transferir el grupo procesado al servidor de base de datos.

Se pueden implantar algoritmos iterativos mediante tres técnicas principales: procesamiento fila por fila, procesamiento de matriz y paralelismo manual.

Procesamiento iterativo: fila por fila

En el proceso fila por fila, un único proceso realiza bucles a través de un juego de datos y funciona en una sola fila a la vez. En una implantación típica, la aplicación recupera cada fila de la base de datos, la procesa en la capa media y, a continuación, envía la fila de nuevo a la base de datos, que ejecuta DML y confirmaciones.

Supongamos que su requisito funcional es consultar una tabla externa denominada ext_scan_events y, a continuación, insertar sus filas en una tabla temporal organizada por pilas denominada stage1_scan_events. El siguiente bloque PL/SQL utiliza una técnica de fila por fila para cumplir este requisito:

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;

La técnica fila por fila tiene las siguientes ventajas:

La técnica fila por fila tiene las siguientes desventajas:

Véase también: Procesamiento basado en conjuntos RWP #7

Procesamiento iterativo: matrices

El proceso de matriz es idéntico al proceso fila por fila, excepto que procesa un grupo de filas en cada iteración en lugar de una sola fila.

Supongamos que el requisito funcional es el mismo que en el ejemplo X-X: consulte una tabla externa denominada ext_scan_events y, a continuación, inserte sus filas en una tabla temporal organizada por pilas denominada stage1_scan_events. El siguiente bloque PL/SQL utiliza una técnica de matriz para cumplir este requisito:

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;

El código anterior difiere del código fila por fila equivalente en el uso de un operador BULK COLLECT en FETCH STATEMENT, que está limitado por el valor array_size del tipo PLS_INTEGER. Por ejemplo, si array_size se define en 100, la aplicación recupera filas en grupos de 100.

La técnica de matriz tiene las siguientes ventajas con respecto a la técnica fila por fila:

Las desventajas de esta técnica son las mismas que para el procesamiento fila por fila. El tiempo de procesamiento puede ser inaceptable para grandes juegos de datos. Además, la aplicación debe ejecutarse en serie en un único núcleo de CPU y, por lo tanto, no puede aprovechar el paralelismo nativo de Oracle Database.

Procesamiento iterativo: paralelismo manual

El paralelismo manual utiliza el mismo algoritmo iterativo que el procesamiento fila por fila y matriz, pero permite que varios procesos de servidor dividan el trabajo y se ejecuten en paralelo.

Supongamos que el requisito funcional es el mismo que el de los ejemplos de fila por fila y matriz. Las principales diferencias son las siguientes:

El siguiente código PL/SQL utiliza el paralelismo manual:

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;

La función ORA_HASH divide la tabla ext_scan_events_dets en 32 cubos distribuidos uniformemente y, a continuación, la sentencia SELECT recupera los nombres de archivo para el cubo 0. Para cada nombre de archivo del cubo, el programa define la ubicación de la tabla externa en este nombre de archivo. A continuación, el programa utiliza el proceso en batch para consultar la tabla externa, insertarla en la tabla temporal y, a continuación, confirmarla.

Mientras se ejecuta el trabajo 1, los otros 31 trabajos de Oracle Scheduler se ejecutan en paralelo. De esta forma, cada trabajo lee simultáneamente un subjuego diferente de los archivos de eventos de exploración e inserta los registros de su subjuego en la misma tabla temporal.

La técnica de paralelismo manual tiene las siguientes ventajas sobre las técnicas iterativas alternativas:

La técnica de paralelismo manual tiene las siguientes desventajas:

Véase también: RWP #8: Procesamiento paralelo basado en conjuntos

Acerca del procesamiento basado en juegos

El procesamiento basado en juegos es una técnica SQL que procesa un juego de datos dentro de la base de datos.

En un modelo basado en juegos, la sentencia SQL define el resultado y permite a la base de datos determinar la forma más eficaz de obtenerlo. Por el contrario, los algoritmos iterativos utilizan la lógica condicional para extraer cada fila o grupo de filas de la base de datos a la aplicación cliente, procesar los datos en el cliente y, a continuación, enviar los datos a la base de datos. El procesamiento basado en juegos elimina la sobrecarga de la API de la base de datos y el recorrido de ida y vuelta de la red porque los datos nunca salen de la base de datos.

Supongamos el mismo requisito funcional que en los ejemplos anteriores. Las siguientes sentencias SQL cumplen este requisito mediante un algoritmo basado en juegos:

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

Debido a que la sentencia INSERT contiene una subconsulta de la tabla ext_scan_events, una sentencia SQL única lee y escribe todas las filas. Además, la aplicación ejecuta una única confirmación después de que la base de datos haya insertado todas las filas. En cambio, las aplicaciones iterativas ejecutan un COMMIT después de insertar cada fila o cada grupo de filas.

La técnica basada en conjuntos tiene ventajas significativas sobre las técnicas iterativas:

El procesamiento basado en juegos tiene algunas desventajas potenciales:

Véase también: 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, y RWP #11: Set-Based Processing–Data Aggregation