2012-06-05 13 views
6

Estoy experimentando un comportamiento interesante al usar funciones definidas por el usuario dentro de una instrucción SELECT.¿Cuándo se evalúan las funciones definidas por el usuario en una consulta en Oracle?

Tengo un par de procedimientos almacenados que leen y eliminan datos de una sola tabla. Estos procedimientos almacenados son utilizados por múltiples fuentes.

En mis observaciones, parece que las funciones definidas por el usuario a veces se evalúan de manera arbitraria, no siempre inmediatamente después o durante la ejecución de la instrucción SELECT que se utiliza en.

Por ejemplo, en un procedimiento almacenado, tengo una instrucción de selección podría ser algo como esto:

SELECT Something, MyFunction(Something) FROM Somewhere; 

esto es seguido por una llamada a otro procedimiento almacenado, que purga datos de la tabla. La cantidad de datos purgados se rige por otra tabla, que almacena la lectura de ID máxima. Esto es para que una purga no elimine ningún dato que aún no haya sido leído por otra instancia de la ejecución del procedimiento almacenado.

En mi código de prueba, MyFunction simplemente devuelve el número de filas en la tabla En algún lugar. Por lo tanto, me imagino que siempre debería ser igual al número de filas que devuelve la instrucción SELECT. Sin embargo, en los casos en que ejecutar dos instancias de este procedimiento almacenado, consigo resultados algo como esto:

Primera instancia de consulta:

Something MyFunction(Something) 
--------- --------------------- 
A   3 
B   3 
C   3 

Segunda instancia de consulta:

Something MyFunction(Something) 
--------- --------------------- 
A   0 
B   0 
C   0  

¿Por qué es que la segunda consulta devuelve todas las filas, pero la función definida por el usuario que opera en la misma tabla informa que no hay más filas en la tabla?

¿De todos modos puedo asegurarme de que la segunda instancia de consulta es coherente en que las funciones definidas por el usuario aún ven los mismos datos que el procedimiento almacenado padre está viendo?

+0

No estoy seguro de entender: entre las 2 llamadas hice y ¿purgas la mesa? – Sebas

+0

Disculpa la confusión. Para aclarar, hay un procedimiento almacenado que (1) realiza la instrucción SELECT y (2) llama al procedimiento de purga después de la instrucción SELECT. – acee

+0

Veo, por lo que la función devuelve el resultado correcto ¿no? El seleccionar no lo hace, o dicho de otra manera, no se debe devolver ninguna fila. ¿Confirmado? – Sebas

Respuesta

9

En general, el problema que está viendo se debe al hecho de que, si bien la consistencia de lectura de múltiples versiones de Oracle asegura que una única declaración SQL siempre verá una vista coherente de los datos, esa misma coherencia no significa que cada SQL La declaración emitida por una función llamada por la declaración SQL original verá el mismo conjunto de datos que la declaración original.

En términos prácticos, esto significa que algo como

SELECT something, 
     COUNT(*) OVER() 
    FROM table_name 

siempre devolverá la respuesta correcta (3 si la consulta devuelve 3 filas), si se pone exactamente la misma lógica en una función

que la instrucción SQL

SELECT something, 
     count_table_name 
    FROM table_name 

no necesariamente devolver un valor º at coincide con el número de filas en la tabla (ni necesariamente devolverá el mismo resultado para cada fila). Puedes verlo en acción si construyes un retraso en tu función para que puedas modificar los datos en una sesión separada.Por ejemplo

SQL> create table foo(col1 number); 

Table created. 

SQL> insert into foo select level from dual connect by level <= 3; 

3 rows created. 

Crear una función que añade un retardo de 10 segundos por cada fila

SQL> ed 
Wrote file afiedt.buf 

    1 create or replace function fn_count_foo 
    2 return number 
    3 is 
    4 l_cnt integer; 
    5 begin 
    6 select count(*) 
    7  into l_cnt 
    8  from foo; 
    9 dbms_lock.sleep(10); 
10 return l_cnt; 
11* end; 
12/

Function created. 

Ahora bien, si en la sesión 1, comienzo a la declaración

select col1, fn_count_foo 
    from foo; 

después pasar a la sesión 2 donde inserto una nueva fila

SQL> insert into foo values(4); 

1 row created. 

SQL> commit; 

Commit complete. 

se puede ver que la función ve la fila recién cometidos durante la segunda ejecución a pesar del hecho de que la propia sentencia SQL sólo ve 3 filas

SQL> select col1, fn_count_foo 
    2 from foo; 

     COL1 FN_COUNT_FOO 
---------- ------------ 
     1   3 
     2   4 
     3   4 

Puede evitar este problema al hacer que su utilización sesión el nivel de aislamiento serializable antes de ejecutar la declaración SQL. Así, por ejemplo,

En la sesión 1, establezca el nivel de aislamiento en serializable y empezar la consulta

SQL> set transaction isolation level serializable; 

Transaction set. 

SQL> select col1, fn_count_foo 
    2 from foo; 

En la sesión 2, insertar una nueva fila

SQL> insert into foo values(5); 

1 row created. 

SQL> commit; 

Commit complete. 

y cuando Sesión 1 regresa 40 segundos después, todo es consistente

SQL> select col1, fn_count_foo 
    2 from foo; 

     COL1 FN_COUNT_FOO 
---------- ------------ 
     1   4 
     2   4 
     3   4 
     4   4 
+0

Ah, gracias por las notas. ¿Cómo establecería esto correctamente en el contexto de un procedimiento almacenado? El procedimiento está siendo llamado por otra aplicación. – acee

+0

@ashyu: lo más habitual es que la aplicación pueda establecer su propio nivel de aislamiento de transacción a menudo como una propiedad de cualquier objeto de tipo 'Conexión' que utilice la aplicación. Puede agregar la línea 'establecer el nivel de aislamiento de transacción' como la primera instrucción del procedimiento también, pero eso solo funcionará si la llamada al procedimiento inicia una nueva transacción: obtendrá un error si una transacción existente intenta llamar el procedimiento porque establecer el nivel de aislamiento tiene que ser la primera operación que hace una transacción. –

+0

+1 Buena explicación Justin. Solo agregaría tener cuidado con el error ORA-08177 (no puede serializar el acceso para esta transacción), que sucederá, por ejemplo, si la sesión 2 actualiza (y confirma) una fila mientras la sesión 1 está ejecutando su consulta larga, y luego la sesión 1 intenta actualizar esa misma fila. Supongo que mi punto es que la mayoría de la gente está acostumbrada a lidiar con el aislamiento de lectura comprometida ya que es el predeterminado. – tbone

Cuestiones relacionadas