Oggi, per concludere questo epico viaggio nel mondo di PL/pgSQL, analizziamo gli errori più comuni che possono aspettarti durante il debug e l’ottimizzazione di funzioni e procedure. Conoscere questi errori ti aiuterà non solo a evitarli in futuro, ma anche a risolvere i bug in modo più efficace se dovessero comparire.
Errori tipici durante il debug e l’ottimizzazione
1. Uso scorretto delle variabili
Uno degli errori più frequenti quando si scrivono e si fanno debug di funzioni in PL/pgSQL è dichiarare o usare male le variabili. Per esempio, se ti dimentichi di specificare esplicitamente il tipo di una variabile o ti confondi con i valori passati tramite i parametri. Vediamo come può succedere nella pratica:
CREATE OR REPLACE FUNCTION calculate_discount(order_total NUMERIC)
RETURNS NUMERIC AS $$
DECLARE
discount_rate NUMERIC;
BEGIN
-- Ops! Ho dimenticato di inizializzare la variabile discount_rate
RETURN order_total * discount_rate;
END;
$$ LANGUAGE plpgsql;
Quando chiami questa funzione otterrai un errore legato all’uso di NULL nei calcoli, perché la variabile discount_rate non è inizializzata.
Come evitarlo:
- Assegna sempre un valore di default alle variabili quando le dichiari:
DECLARE
discount_rate NUMERIC := 0.1; -- Valore di default
- Controlla l’uso delle variabili con
RAISE NOTICEper assicurarti che abbiano i valori che ti aspetti:
RAISE NOTICE 'Valore di discount_rate: %', discount_rate;
2. Mancanza di logging degli errori
Un altro problema comune è la mancanza di un meccanismo di logging. Se qualcosa va storto e non logghi cosa succede nella tua funzione, è come cercare un gatto nero in una stanza buia, soprattutto se non sei nemmeno sicuro che il gatto ci sia davvero.
Ecco un esempio di funzione senza logging:
CREATE OR REPLACE FUNCTION process_order(order_id INT)
RETURNS VOID AS $$
BEGIN
-- Logica complicata di gestione dell’ordine
UPDATE orders SET status = 'processed' WHERE id = order_id;
END;
$$ LANGUAGE plpgsql;
E se order_id viene passato in modo sbagliato? E se la riga nella tabella orders non esiste?
Come evitarlo: Aggiungi RAISE NOTICE o RAISE EXCEPTION per loggare i passaggi critici:
CREATE OR REPLACE FUNCTION process_order(order_id INT)
RETURNS VOID AS $$
BEGIN
-- Logghiamo i dati in ingresso
RAISE NOTICE 'Gestione dell’ordine con ID %', order_id;
-- Logica complicata di gestione
UPDATE orders SET status = 'processed' WHERE id = order_id;
-- Logghiamo il risultato
RAISE NOTICE 'Stato dell’ordine aggiornato per ID %', order_id;
END;
$$ LANGUAGE plpgsql;
Così puoi facilmente capire dove si verifica l’errore grazie ai messaggi stampati.
3. Ignorare la performance delle query
Questo è uno dei principali nemici di chi lavora con i database. Per esempio, scrivi una funzione che sembra ok, ma va lentissima. Una delle cause principali delle query lente è la mancanza di indici o piani di esecuzione inefficienti.
Esempio di query lenta:
CREATE OR REPLACE FUNCTION get_large_orders()
RETURNS TABLE(order_id INT, total NUMERIC) AS $$
BEGIN
RETURN QUERY
SELECT id, total FROM orders WHERE total > 1000;
END;
$$ LANGUAGE plpgsql;
Se il campo total nella tabella orders non è indicizzato, la query farà una scansione completa della tabella, che è super inefficiente.
Come evitarlo:
- Usa
EXPLAIN ANALYZEper controllare l’efficienza delle query:
EXPLAIN ANALYZE SELECT id, total FROM orders WHERE total > 1000;
- Crea indici sulle colonne usate spesso:
CREATE INDEX idx_orders_total ON orders(total);
4. Uso del livello di isolamento delle transazioni sbagliato
Quando esegui procedure complesse, a volte capitano errori perché non si capisce bene come funzionano i livelli di isolamento delle transazioni. Per esempio, se due transazioni cercano di aggiornare la stessa riga contemporaneamente, può succedere un deadlock.
Esempio di potenziale deadlock:
BEGIN;
UPDATE orders SET status = 'processed' WHERE id = 1;
-- Aspettando il lock di un’altra transazione
UPDATE inventory SET stock = stock - 1 WHERE product_id = 100;
COMMIT;
Se un’altra transazione prova a fare queste operazioni in ordine diverso, ottieni un blocco reciproco.
Come evitarlo:
- Pensa bene all’ordine delle operazioni e seguilo sempre.
- Usa il livello di isolamento
SERIALIZABLEse serve.
5. Mancanza di gestione degli errori
Gestire gli errori non è solo una buona pratica, ma anche uno strumento per rendere il tuo codice più stabile. Per esempio, nel codice qui sotto manca la gestione degli errori possibili:
CREATE OR REPLACE FUNCTION add_order(order_id INT)
RETURNS VOID AS $$
BEGIN
INSERT INTO orders (id, status) VALUES (order_id, 'new');
END;
$$ LANGUAGE plpgsql;
Se per caso order_id esiste già, otterrai l’errore duplicate key value violates unique constraint.
Come evitarlo: Usa i blocchi di gestione delle eccezioni:
CREATE OR REPLACE FUNCTION add_order(order_id INT)
RETURNS VOID AS $$
BEGIN
INSERT INTO orders (id, status) VALUES (order_id, 'new');
EXCEPTION WHEN unique_violation THEN
RAISE NOTICE 'Ordine con ID % esiste già!', order_id;
END;
$$ LANGUAGE plpgsql;
Esempi di errori e come correggerli
Errore 1: Le query sono lente perché manca l’indicizzazione
Situazione: Hai una query che filtra una tabella per una colonna, ma quella colonna non ha un indice.
Correzione: Crea un indice sulla colonna giusta.
Errore 2: La logica della funzione è confusa e difficile da debuggare
Situazione: La funzione contiene troppa logica e non è suddivisa in sotto-funzioni.
Correzione: Dividi la funzione complessa in funzioni più piccole. Così il codice è più leggibile e il debug è più semplice.
Errore 3: Uso scorretto di RAISE EXCEPTION
Situazione: RAISE EXCEPTION viene usato per tutti gli errori, anche quelli poco importanti.
Correzione: Usa RAISE NOTICE per i messaggi informativi e solo RAISE EXCEPTION per i casi critici.
RAISE NOTICE 'Tutto sotto controllo — questa fase della funzione è finita.';
RAISE EXCEPTION 'Qualcosa si è rotto! Dai un’occhiata ai parametri di input.';
Consigli per evitare errori
- Aggiungi logging: nei punti critici della tua funzione usa
RAISE NOTICEper tracciare cosa succede. - Testa le funzioni: usa regolarmente dati di test per controllare funzioni e procedure.
- Mantieni il codice leggibile: dividi le funzioni complesse in sotto-funzioni e procedure più piccole.
- Analizza la performance: usa
EXPLAIN ANALYZEper assicurarti che le tue query vadano veloci. - Preparati agli imprevisti: aggiungi sempre blocchi di gestione delle eccezioni per gestire gli errori.
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Si è verificato un errore imprevisto: %', SQLERRM;
Così potrai gestire gli errori con sicurezza e prevenirli in futuro.
GO TO FULL VERSION