2011-06-08 6 views
18

Si spera che una semplice domanda, ma per la quale non ho trovato una risposta decente. Sono informato in modo affidabile che le stored procedure (funzioni DB definite dall'utente) in PostgreSQL (in particolare, versione 9.0.4) sono intrinsecamente transazionali, in quanto vengono chiamate attraverso un'istruzione SELECT che a sua volta è una transazione. Quindi, come si sceglie il livello di isolamento della stored procedure? Credo che in altri DBMS il blocco transazionale desiderato sia racchiuso in un blocco START TRANSACTION per il quale il livello di isolamento desiderato è un parametro facoltativo.imposta il livello di isolamento per le stored procedure postgresql

Come esempio specifico made-up, dire che voglio fare questo:

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 immaginate io voglio assicurarmi che questa funzione viene sempre eseguita come una transazione serializzabile (sì, sì, PostgreSQL isn SERIALIZABLE È corretto serializzabile, ma non è questo il punto). Non voglio richiedere che venga chiamato come

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

Quindi, come si inserisce il livello di isolamento richiesto nella funzione? Credo che non si può semplicemente mettere il livello di isolamento nella dichiarazione BEGIN, come the manual says

E 'importante non confondere l'uso di BEGIN/END per raggruppare dichiarazioni in PL/pgSQL con il nome simile SQL comandi per il controllo delle transazioni. BEGIN/END PL/pgSQL sono solo per il raggruppamento ; non avviano o terminano una transazione . Funzioni e innescare procedure vengono sempre eseguiti all'interno una transazione stabilita da un esterno interrogazione - non possono iniziare o commettere tale operazione, dal momento che non ci sarebbe alcun contesto per loro di eseguire in

La più ovvia. approccio per me sarebbe quella di utilizzare SET TRANSACTION da qualche parte nella definizione della funzione, ad esempio ,:

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; 

Anche se questo sarebbe stato accettato, non è chiaro di quanto io possa contare su questo per lavorare. Il documentation per SET TRANSACTION dice

Se SET TRANSACTION viene eseguito senza uno START prima transazione o BEGIN, apparirà avere alcun effetto, dal momento che la transazione immediatamente fine.

Il che mi lascia perplesso, dal momento che se chiamo un solitario SELECT add_new_row('foo'); dichiarazione mi aspetterei (fornito non ho disabilitato autocommit) SELECT essere in esecuzione come una transazione singola linea con il livello di isolamento sessione predefinita.

Il manual dice anche: livello di isolamento

La transazione non può essere modificato dopo la prima query o dichiarazione di modifica dati (SELECT, INSERT, DELETE, UPDATE, FETCH o COPIA) di una transazione è stata eseguita .

Che cosa succede se la funzione viene chiamata dall'interno di una transazione con un livello di isolamento più basso, per esempio,:.

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

Per una domanda bonus: non il linguaggio della funzione fa alcuna differenza? Si potrebbe impostare il livello di isolamento in modo diverso in PL/pgSQL rispetto a SQL semplice?

Sono un fan degli standard e delle best practice documentate, quindi qualsiasi riferimento decente sarebbe apprezzato.

+1

Che cosa è successo quando si è tentato di utilizzare 'SET TRANSACTION' all'interno della funzione? –

+0

@a_horse_with_no_name: come ho detto, immagino che "SET TRANSACTION" sia ciò di cui ho bisogno e le funzioni con esso sono accettate, ma a volte ciò non significa molto (alcune opzioni vengono ingoiate a volte), quindi sto cercando un approccio documentato piuttosto che qualcosa che sembra funzionare. – beldaz

+1

Postgres ingerisce raramente quello che gli dici di fare - e quando lo fa mi aspetterei che emettesse un avvertimento. –

risposta

15

Non puoi farlo.

Ciò che si potrebbe fare è verificare la funzionalità del livello di isolamento della transazione corrente e interrompere se non è quello desiderato. È possibile farlo eseguendo SELECT current_setting('transaction_isolation') e quindi controllando il risultato.

+0

Sì, stavo piuttosto tornando a pensare che sarebbe stato l'unico approccio. Penso che la tua "anti-risposta" sia la cosa più vicina a ciò che voglio;) – beldaz

1

La lingua della funzione non fa alcuna differenza.

Questo fallisce:

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 

Si noti che nel vostro caso particolare, si potrebbe fare questo usando un innesco sul tuo primo tavolo. Basta fare in modo che gli aggiornamenti di conteggio delle righe vengano eseguiti in a consistent order per evitare i dead-lock, e andrà bene in modalità ripetibile.

sono un fan di standard

il codice PL/lingue sono piattaforma specifica.

+0

Ti dispiacerebbe ampliare la tua risposta mostrando dove dovrebbe essere impostato? Come ho detto, l'esempio è stato interamente inventato, quindi non intendevo ingannarti nel pensare che stavo seguendo un approccio diverso con i trigger. Per quanto riguarda i linguaggi PL che sono specifici della piattaforma, possono comunque avere documentazione. Trovo difficile trovare la documentazione di postgres per dare una risposta adeguata a qualcosa di specifico, quindi le indicazioni su dove esattamente guardare potrebbero essere utili a qualsiasi altro lettore. – beldaz

+0

Basta includerlo nel corpo della funzione ... Sostituisci 'begin' con' begin isolation level serializable' (o aggiungi un extra begin/end block, se tosse un errore). –

+0

@Denis: la sostituzione di 'begin' come suggerito sembrerebbe essere in conflitto con postgres v9 manual 39.2 -" BEGIN/END di PL/pgSQL sono solo per il raggruppamento, non avviano o terminano una transazione ". – beldaz

0

Isolamento della transazione indica quali modifiche apportate in altre transazioni concorsuali a cui è possibile accedere.

Se si desidera serializzare l'esecuzione, è necessario utilizzare i blocchi.

È possibile utilizzare dopo il trigger di riga e il conteggio di aggiornamento. "UPDATE row_counts_table" bloccherà la tabella e tutte le transazioni saranno serializzate. È lento.

Nel tuo esempio hai due dichiarazioni. Inserisci viene eseguito ma l'aggiornamento deve attendere altre transazioni e il conteggio non è valido in questo periodo.

+0

10 anni fa, forse. Ma se intendevi in ​​generale, allora no, la serializzazione non deve usare i lucchetti. Lo standard SQL originale supponeva che i blocchi sarebbero stati utilizzati, motivo per cui i livelli di isolamento si basano su quali anomalie specifiche del blocco si è disposti ad accettare. Tuttavia, l'approccio MVCC di postgres e SQL Server è fondamentalmente diverso dal locking, e sebbene vi sia il problema dell'asimmetria di scrittura, sono stati escogitati metodi (ad esempio, da Fekete et al) per evitare questo e fornire così l'esecuzione serializzabile. – beldaz

0

In PG le vostre procedure non sono transazioni separate. Questa è la stored procedure che prende parte a una transazione esistente.

BEGIN TRAN 

SELECT 1; 
SELECT my_proc(99); 

ROLLBACK TRAN; 

Detto questo è necessario impostare il livello di transazione in cui la transazione si avvia, che è al di fuori della stored procedure.

Un'opzione potrebbe essere quella di configurare il server in modo che venga eseguito nell'isolamento che si desidera principalmente utilizzare e di eseguire un SET per i casi limite in cui è diverso dalle impostazioni del server.

+0

Ahimè, che sconfigge il beneficio delle stored procedure per me, nel senso che voglio che alcune operazioni avvengano insieme in un'unità server-side con uno specifico livello di isolamento della transazione. Si pensi di accreditare gli account con interessi, per i quali non si desidera utilizzare il valore del conto corrente intermittente se alcune altre transazioni eseguivano un trasferimento di saldo. Sarebbe un peccato se si dovesse ricorrere a transazioni lato client per questo, e sperare che il livello di transazione predefinito della sessione fosse corretto non è esattamente infallibile. – beldaz