2011-06-08 16 views
18

Afortunadamente una pregunta simple, pero para la que no he encontrado una respuesta decente. Estoy informado de manera confiable que los procedimientos almacenados (funciones de base de datos definidas por el usuario) en PostgreSQL (específicamente, la versión 9.0.4) son inherentemente transaccionales, en la medida en que son llamados a través de una instrucción SELECT que a su vez es una transacción. Entonces, ¿cómo se elige el nivel de aislamiento del procedimiento almacenado? Creo en otros DBMS que el bloque transaccional deseado estaría envuelto en un bloque START TRANSACTION para el cual el nivel de aislamiento deseado es un parámetro opcional.establece el nivel de aislamiento para los procedimientos almacenados de postgresql

Como ejemplo maquillada específica, dicen que yo quiero hacer esto:

CREATE FUNCTION add_new_row(rowtext TEXT) 
RETURNS VOID AS 
$$ 
BEGIN 
     INSERT INTO data_table VALUES (rowtext); 
     UPDATE row_counts_table SET count=count+1; 
END; 
$$ 
LANGUAGE plpgsql 
SECURITY DEFINER; 

E imagina que quiero para asegurarse de que esta función siempre se realiza como una transacción serializable (sí, sí, PostgreSQL ISN SERIALIZABLE no es serializable, pero ese no es el punto). No deseo requerir que se llame como

START TRANSACTION ISOLATION LEVEL SERIALIZABLE; 
SELECT add_new_row('foo'); 
COMMIT; 

Entonces, ¿cómo presiono el nivel de aislamiento requerido hacia abajo en la función? Creo que no puedo acaba de poner el nivel de aislamiento en la declaración BEGIN, como the manual says

Es importante no confundir el uso de BEGIN/END para agrupar declaraciones en PL/pgSQL con el nombre similar SQL comandos para control de transacciones PL/pgSQL's BEGIN/END son solo para la agrupación ; no comienzan ni finalizan una transacción . Funciones y desencadenar procedimientos se ejecutan siempre dentro una transacción establecida por una consulta externa - no pueden iniciar o cometen esa transacción, ya que no habría ningún contexto para que se ejecuten en

La más obvia. enfoque para mí sería utilizar SET TRANSACTION algún lugar de la definición de la función, por ejemplo ,:

CREATE FUNCTION add_new_row(rowtext TEXT) 
RETURNS VOID AS 
$$ 
BEGIN 
     SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 
     INSERT INTO data_table VALUES (rowtext); 
     UPDATE row_counts_table SET count=count+1; 
END; 
$$ 
LANGUAGE plpgsql 
SECURITY DEFINER; 

Si bien esto sería aceptado, no está claro lo que puedo confiar en que esto funcione. El documentation para SET TRANSACTION dice

Si SET operación se ejecuta sin un comienzo o transacción antes de empezar, que se parecen tener ningún efecto, ya que la transacción terminará inmediatamente.

Lo que me deja perplejo, ya que si llamo una declaración solitaria SELECT add_new_row('foo'); Yo esperaría que (siempre que no se ha desactivado confirmación automática) SELECT que se ejecuta como una transacción de una sola línea con el nivel de aislamiento predeterminado sesión.

El manual también dice: nivel de aislamiento

La transacción no puede ser cambiado después de la primera consulta o declaración de modificación de datos (SELECT, INSERT, DELETE, UPDATE, FETCH o COPIA) de se ha ejecutado una transacción .

Entonces, ¿qué sucede si la función se llama desde dentro de una transacción con un nivel de aislamiento inferior, por ejemplo,:.

START TRANSACTION ISOLATION LEVEL READ COMMITTED; 
UPDATE row_counts_table SET count=0; 
SELECT add_new_row('foo'); 
COMMIT; 

Para una pregunta extra: ¿el lenguaje de la función alguna diferencia? ¿Se establecería el nivel de aislamiento de forma diferente en PL/pgSQL que en SQL simple?

Soy un fanático de los estándares y las mejores prácticas documentadas, por lo que cualquier referencia decente sería apreciada.

+1

¿Qué pasó cuando trataste de usar 'SET TRANSACTION' dentro de la función? –

+0

@a_horse_with_no_name: Como mencioné, me imagino que 'SET TRANSACTION' es lo que necesito, y las funciones con él son aceptadas, pero a veces eso no significa mucho (algunas opciones simplemente se tragan a veces), así que estoy buscando un enfoque documentado en lugar de algo que simplemente parece funcionar. – beldaz

+1

Postgres rara vez se traga lo que le dices que haga, y cuando lo haga, esperaría que emita una advertencia. –

Respuesta

15

No puede hacer eso.

Lo que podría hacer es que su función verifique cuál es el nivel de aislamiento de transacción actual y cancelar si no es el que desea. Puede hacerlo ejecutando SELECT current_setting('transaction_isolation') y luego verificando el resultado.

+0

Sí, estaba pensando que iba a ser el único enfoque. Creo que su "anti-respuesta" es lo más parecido a lo que quiero;) – beldaz

1

El idioma de la función no hace diferencia alguna.

Esta falla:

test=# create function test() returns int as $$ 
    set transaction isolation level serializable; 
    select 1; 
$$ language sql; 
CREATE FUNCTION 
test=# select test(); 
ERROR: SET TRANSACTION ISOLATION LEVEL must be called before any query 
CONTEXT: SQL function "test" statement 1 

Tenga en cuenta que en su caso particular, se puede hacer esto usando un disparador en su primera tabla. Solo asegúrese de que las actualizaciones de recuento de filas se realicen en in a consistent order para evitar bloqueos irrecuperables, y lo hará bien en el modo de lectura repetible.

Soy un fan de las normas

PL/idiomas están plataforma específica.

+0

¿Te importaría expandir tu respuesta mostrando dónde se debe establecer? Como mencioné, el ejemplo fue completamente inventado, así que no fue mi intención engañarlo para que pensara que buscaba un enfoque diferente con desencadenantes. En cuanto a los lenguajes PL que son específicos de la plataforma, aún pueden tener documentación. Me resulta difícil encontrar la documentación de postgres para obtener una respuesta adecuada a algo específico, por lo que las indicaciones de dónde buscar exactamente me serían útiles a otros lectores. – beldaz

+0

Simplemente inclúyalo en el cuerpo de la función ... Reemplace 'begin' por' begin isolation level serializable' (o agregue un bloque adicional de inicio/final, si se produce un error). –

+0

@Denis: reemplazar 'begin' como sugiere podría entrar en conflicto con el manual de postgres v9 39.2 -" Los BEGIN/END de PL/pgSQL son solo para agrupar, no inician ni finalizan una transacción ". – beldaz

0

El aislamiento de transacción significa qué cambios se realizan en otras transacciones concurent a las que puede acceder.

Si desea serializar la ejecución, debe usar bloqueos.

Puede utilizar después del desencadenador de fila y conteo de actualización. "UPDATE row_counts_table" bloqueará la tabla y todas las transacciones se serializarán. Es lento.

En su ejemplo tiene dos afirmaciones. Insert se ejecuta pero la actualización tiene que esperar otras transacciones y el recuento no es válido en este período.

+0

Hace 10 años, tal vez. Pero si te refieres en general, entonces no, la serialización no tiene que usar bloqueos. El estándar SQL original suponía que se usarían bloqueos, por lo que los niveles de aislamiento se basan en las anomalías específicas de bloqueo que uno está dispuesto a aceptar. Sin embargo, el enfoque MVCC por postgres y SQL Server es fundamentalmente diferente al bloqueo, y aunque existe el problema del sesgo de escritura, se han ideado métodos (por ejemplo, por Fekete et al) para evitar esto y así proporcionar una ejecución serializable. – beldaz

0

En PG sus procedimientos no son transacciones separadas. Es decir, el procedimiento almacenado participa en una transacción existente.

BEGIN TRAN 

SELECT 1; 
SELECT my_proc(99); 

ROLLBACK TRAN; 

Con eso dijo que hay que establecer el nivel de transacción en la que la transacción se inicia que está fuera del procedimiento almacenado.

Una opción sería configurar el servidor para que se ejecute en el aislamiento que más desea utilizar y realizar un SET para los casos extremos en los que difiera de la configuración del servidor.

+0

Desafortunadamente, eso me hace perder el beneficio de los procedimientos almacenados, ya que quiero que algunas operaciones ocurran juntas en una unidad del lado del servidor con un nivel de aislamiento de transacción. Piense en acreditar cuentas con intereses, para las cuales no desea utilizar el valor de la cuenta corriente intermitente si alguna otra transacción estaba realizando una transferencia de saldos. Sería una pena si uno tuviera que recurrir a transacciones del lado del cliente para esto, y esperar que el nivel de transacción predeterminado de la sesión fuera correcto no es exactamente infalible. – beldaz

Cuestiones relacionadas