Dzisiaj, żeby zakończyć tę epicką podróż przez PL/pgSQL, przeanalizujemy najczęstsze błędy, które mogą cię dopaść podczas debugowania i optymalizacji funkcji oraz procedur. Znajomość tych błędów pomoże ci nie tylko unikać problemów w przyszłości, ale też szybciej ogarniać bugi, jeśli jednak się pojawią.
Typowe błędy podczas debugowania i optymalizacji
1. Nieprawidłowe użycie zmiennych
Jednym z najczęstszych błędów podczas pisania i debugowania funkcji w PL/pgSQL jest złe zadeklarowanie lub użycie zmiennych. Na przykład, jeśli zapomnisz jawnie określić typ zmiennej albo pogubisz się w wartościach przekazywanych przez parametry. Zobaczmy, jak to może wyglądać w praktyce:
CREATE OR REPLACE FUNCTION calculate_discount(order_total NUMERIC)
RETURNS NUMERIC AS $$
DECLARE
discount_rate NUMERIC;
BEGIN
-- Ups! Zapomniałem zainicjalizować zmienną discount_rate
RETURN order_total * discount_rate;
END;
$$ LANGUAGE plpgsql;
Wywołując tę funkcję dostaniesz błąd związany z użyciem NULL w obliczeniach, bo zmienna discount_rate na starcie nie jest zainicjalizowana.
Jak tego uniknąć:
- Zawsze przypisuj zmiennym wartości domyślne podczas deklaracji:
DECLARE
discount_rate NUMERIC := 0.1; -- Wartość domyślna
- Sprawdzaj wartości zmiennych za pomocą
RAISE NOTICE, żeby mieć pewność, że mają to, czego oczekujesz:
RAISE NOTICE 'Wartość discount_rate: %', discount_rate;
2. Brak logowania błędów
Kolejny popularny problem to brak mechanizmu logowania. Jeśli coś pójdzie nie tak, a nie logujesz przebiegu swojej funkcji, to jak szukanie czarnego kota w ciemnym pokoju, zwłaszcza jeśli nie masz pewności, czy ten kot tam w ogóle jest.
Oto przykład funkcji bez logowania:
CREATE OR REPLACE FUNCTION process_order(order_id INT)
RETURNS VOID AS $$
BEGIN
-- Jakaś skomplikowana logika obsługi zamówienia
UPDATE orders SET status = 'processed' WHERE id = order_id;
END;
$$ LANGUAGE plpgsql;
A co jeśli order_id jest przekazany niepoprawnie? Albo jeśli rekord w tabeli orders nie istnieje?
Jak tego uniknąć: Dodaj RAISE NOTICE albo RAISE EXCEPTION, żeby logować kluczowe kroki:
CREATE OR REPLACE FUNCTION process_order(order_id INT)
RETURNS VOID AS $$
BEGIN
-- Logujemy dane wejściowe
RAISE NOTICE 'Obsługa zamówienia z ID %', order_id;
-- Skomplikowana logika obsługi
UPDATE orders SET status = 'processed' WHERE id = order_id;
-- Logujemy wynik
RAISE NOTICE 'Status zamówienia zaktualizowany dla ID %', order_id;
END;
$$ LANGUAGE plpgsql;
Teraz łatwo namierzyć, gdzie pojawia się błąd, dzięki wyświetlanym komunikatom.
3. Ignorowanie wydajności zapytań
To jeden z głównych wrogów każdego developera baz danych. Na przykład, piszesz funkcję, która wygląda OK, ale działa strasznie wolno. A jedną z głównych przyczyn wolnych zapytań jest brak indeksów albo nieefektywne plany wykonania zapytań.
Przykład wolnego zapytania:
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;
Jeśli pole total w tabeli orders nie jest zindeksowane, zapytanie przeskanuje całą tabelę, co jest mega nieefektywne.
Jak tego uniknąć:
- Używaj
EXPLAIN ANALYZE, żeby sprawdzić wydajność zapytań:
EXPLAIN ANALYZE SELECT id, total FROM orders WHERE total > 1000;
- Twórz indeksy na często używanych kolumnach:
CREATE INDEX idx_orders_total ON orders(total);
4. Użycie złego poziomu izolacji transakcji
Podczas wykonywania złożonych procedur czasem pojawiają się błędy przez złe zrozumienie poziomów izolacji transakcji. Na przykład, jeśli dwie transakcje jednocześnie próbują zaktualizować ten sam rekord, może dojść do deadlock.
Przykład potencjalnego deadlock:
BEGIN;
UPDATE orders SET status = 'processed' WHERE id = 1;
-- Oczekiwanie na blokadę innej transakcji
UPDATE inventory SET stock = stock - 1 WHERE product_id = 100;
COMMIT;
Jeśli inna transakcja wykona te operacje w innej kolejności, dostaniesz wzajemną blokadę.
Jak tego uniknąć:
- Przemyśl kolejność wykonywania operacji i trzymaj się jej.
- Używaj poziomu izolacji
SERIALIZABLE, jeśli to konieczne.
5. Brak obsługi błędów
Obsługa błędów to nie tylko dobra praktyka, ale też narzędzie do zapewnienia stabilności twojego kodu. Na przykład, w poniższym kodzie nie ma obsługi potencjalnych błędów:
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;
Jeśli nagle order_id już istnieje, dostaniesz błąd duplicate key value violates unique constraint.
Jak tego uniknąć: Użyj bloków obsługi wyjątków:
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 'Zamówienie z ID % już istnieje!', order_id;
END;
$$ LANGUAGE plpgsql;
Przykłady błędów i ich poprawa
Błąd 1: Zapytania działają wolno przez brak indeksowania
Sytuacja: Masz zapytanie, które filtruje tabelę po kolumnie, ale ta kolumna nie ma indeksu.
Poprawa: Utwórz indeks dla odpowiedniej kolumny.
Błąd 2: Logika funkcji jest zagmatwana i trudno ją debugować
Sytuacja: Funkcja zawiera za dużo logiki i nie jest podzielona na podfunkcje.
Poprawa: Podziel złożoną funkcję na mniejsze podfunkcje. To poprawi czytelność i ułatwi debugowanie.
Błąd 3: Nieprawidłowe użycie RAISE EXCEPTION
Sytuacja: RAISE EXCEPTION jest używane do wszystkich błędów, nawet tych mało istotnych.
Poprawa: Używaj RAISE NOTICE do komunikatów informacyjnych, a RAISE EXCEPTION tylko do krytycznych przypadków.
RAISE NOTICE 'Wszystko pod kontrolą — bieżący etap funkcji zakończony.';
RAISE EXCEPTION 'Coś się zepsuło! Sprawdź parametry wejściowe.';
Wskazówki jak zapobiegać błędom
- Dodawaj logowanie: na kluczowych etapach funkcji używaj
RAISE NOTICEdo śledzenia przebiegu. - Testuj funkcje: regularnie korzystaj z danych testowych do sprawdzania funkcji i procedur.
- Dbaj o czytelność kodu: dziel złożone funkcje na mniejsze podfunkcje i procedury.
- Analizuj wydajność: używaj
EXPLAIN ANALYZE, żeby mieć pewność, że twoje zapytania działają sprawnie. - Bądź gotowy na niespodzianki: zawsze dodawaj bloki obsługi wyjątków do obsługi błędów.
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Wystąpił nieoczekiwany błąd: %', SQLERRM;
Dzięki temu pewniej ogarniesz błędy, a także zapobiegniesz ich pojawianiu się w przyszłości.
GO TO FULL VERSION