Çoxmərhələli prosedurlar — bunlar database üçün "isveç bıçağı" kimidir. Onlar adətən input dataların validasiyası, dəyişikliklərin icrası (məsələn, record-ların yenilənməsi, logların əlavə olunması), bəzən isə analitikadan ibarət olur. Amma problem burdadır: prosedur nə qədər mürəkkəbdirsə, səhv ehtimalı da bir o qədər artır. Məntiq səhvi, yavaş query, gözdən qaçan detal — və hər şey alt-üst ola bilər.
Kompleks debug aşağıdakı aspektləri əhatə edir:
- Input dataların analizi: parametrlər düzgün qurulub? Düzgün datalar ötürülüb?
- Əsas mərhələlərin icrasının yoxlanması: prosedurun bütün addımları düzgün işləyir?
- Aralıq nəticələrin loglanması: nə baş verdiyini bilmək üçün, nəsə "qırılmamışdan" əvvəl.
- Performance bottleneck-lərin optimizasiyası: query-ləri "yavaşdıran" zəif yerləri yaxşılaşdırırıq.
Tapşırığın qoyuluşu: çoxmərhələli prosedur nümunəsi
Praktiki nümunə üçün təsəvvür elə ki, biz internet mağazasının database-i ilə işləyirik. Bizə sifarişin işlənməsi üçün prosedur yaratmaq lazımdır. O, aşağıdakı addımları yerinə yetirəcək:
- Məhsulun anbarda olub-olmadığını yoxlamaq.
- Məhsulu rezerv etmək.
- Sifarişin statusunu yeniləmək.
- Hadisələri (məsələn, uğurlu rezervasiya və ya səhv) log cədvəlinə yazmaq.
Database strukturunun script-i:
-- Məhsullar cədvəli
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
stock_quantity INTEGER NOT NULL
);
-- Sifarişlər cədvəli
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
product_id INTEGER REFERENCES products(product_id),
order_status TEXT NOT NULL
);
-- Log cədvəli
CREATE TABLE order_logs (
log_id SERIAL PRIMARY KEY,
order_id INTEGER,
log_message TEXT,
log_time TIMESTAMP DEFAULT NOW()
);
Addım 1: Çoxmərhələli prosedurun yaradılması
Gəlin process_order adlı əsas proseduru yaradaq. O, sifarişin id-sini qəbul edib bütün işləmə mərhələlərini icra edəcək.
CREATE OR REPLACE FUNCTION process_order(p_order_id INTEGER)
RETURNS VOID LANGUAGE plpgsql AS $$
DECLARE
v_product_id INTEGER;
v_stock_quantity INTEGER;
BEGIN
-- 1. Məhsulun id-sini və sifarişin statusunu alırıq
SELECT product_id INTO v_product_id
FROM orders
WHERE order_id = p_order_id;
IF v_product_id IS NULL THEN
RAISE EXCEPTION 'Sifariş % mövcud deyil və ya product_id yoxdur', p_order_id;
END IF;
-- 2. Məhsulun anbarda olub-olmadığını yoxlayırıq
SELECT stock_quantity INTO v_stock_quantity
FROM products
WHERE product_id = v_product_id;
IF v_stock_quantity <= 0 THEN
RAISE EXCEPTION 'Məhsul % anbarda yoxdur', v_product_id;
END IF;
-- 3. Anbardakı miqdarı yeniləyirik
UPDATE products
SET stock_quantity = stock_quantity - 1
WHERE product_id = v_product_id;
-- 4. Sifarişin statusunu yeniləyirik
UPDATE orders
SET order_status = 'İşləndi'
WHERE order_id = p_order_id;
-- 5. Uğurlu hadisəni log-a yazırıq
INSERT INTO order_logs(order_id, log_message)
VALUES (p_order_id, 'Sifariş uğurla işlənib.');
END;
$$;
Addım 2: RAISE NOTICE və RAISE EXCEPTION ilə error loglamaq
İndi isə əsl "sehr" başlayır. Addımların arasında loglama əlavə edəcəyik ki, error-ları tutaq və hər mərhələdə nə baş verdiyini başa düşək.
Loglamalı yenilənmiş kod:
CREATE OR REPLACE FUNCTION process_order(p_order_id INTEGER)
RETURNS VOID LANGUAGE plpgsql AS $$
DECLARE
v_product_id INTEGER;
v_stock_quantity INTEGER;
BEGIN
RAISE NOTICE 'Sifariş işlənir %...', p_order_id;
-- 1. Məhsulun id-sini alırıq
SELECT product_id INTO v_product_id
FROM orders
WHERE order_id = p_order_id;
IF v_product_id IS NULL THEN
RAISE EXCEPTION 'Sifariş % mövcud deyil və ya product_id yoxdur', p_order_id;
END IF;
RAISE NOTICE 'Sifariş üçün məhsul ID-si %: %', p_order_id, v_product_id;
-- 2. Məhsulun anbarda olub-olmadığını yoxlayırıq
SELECT stock_quantity INTO v_stock_quantity
FROM products
WHERE product_id = v_product_id;
IF v_stock_quantity <= 0 THEN
RAISE EXCEPTION 'Məhsul % anbarda yoxdur', v_product_id;
END IF;
RAISE NOTICE 'Məhsulun % anbar miqdarı: %', v_product_id, v_stock_quantity;
-- 3. Anbardakı miqdarı yeniləyirik
UPDATE products
SET stock_quantity = stock_quantity - 1
WHERE product_id = v_product_id;
-- 4. Sifarişin statusunu yeniləyirik
UPDATE orders
SET order_status = 'İşləndi'
WHERE order_id = p_order_id;
-- 5. Uğurlu icranı loglayırıq
INSERT INTO order_logs(order_id, log_message)
VALUES (p_order_id, 'Sifariş uğurla işlənib.');
RAISE NOTICE 'Sifariş % uğurla işlənib.', p_order_id;
EXCEPTION WHEN OTHERS THEN
-- Error-u loglayırıq
INSERT INTO order_logs(order_id, log_message)
VALUES (p_order_id, 'Səhv: ' || SQLERRM);
RAISE;
END;
$$;
Addım 3: İndex-lərlə optimizasiya
Əgər database-də çoxlu məhsul və ya sifariş varsa, lazım olan sətri tapmaq bottleneck ola bilər. İndex əlavə edək ki, işləmə zamanı seçim sürətlənsin:
-- orders cədvəlində axtarışı sürətləndirmək üçün index
CREATE INDEX idx_orders_product_id ON orders(product_id);
-- products cədvəlində axtarışı sürətləndirmək üçün index
CREATE INDEX idx_products_stock_quantity ON products(stock_quantity);
Addım 4: EXPLAIN ANALYZE ilə performans analizi
İndi yoxlayaq görək, funksiyamız nə qədər tez işləyir. Bunun üçün onu performans analizi ilə çağırırıq:
EXPLAIN ANALYZE
SELECT process_order(1);
Nəticə göstərəcək ki, hər mərhələ nə qədər vaxt aparır. Hansı addım daha yavaşdır — bunu tapıb proseduru daha da optimizasiya edə bilərik.
Addım 5: Transaction-lardan istifadə ilə təkmilləşdirmə
Etibarlılığı artırmaq üçün bütün proseduru transaction-a bükmək olar. Beləliklə, nəsə səhv getsə, bütün dəyişikliklər geri qaytarılacaq.
BEGIN;
-- Funksiyanı çağırırıq
SELECT process_order(1);
-- Transaction-u commit edirik
COMMIT;
Funksiya kodunun özündə SAVEPOINT və ROLLBACK TO SAVEPOINT istifadə edib qismən error-ları idarə etmək olar.
Praktiki tapşırıq: kütləvi sifarişlərin işlənməsi
Dərsi bir neçə sifarişin eyni anda işlənməsi nümunəsi ilə bitirək. Elə bir funksiya yaradacağıq ki, statusu Pending olan bütün sifarişləri işləsin:
CREATE OR REPLACE FUNCTION process_all_orders()
RETURNS VOID LANGUAGE plpgsql AS $$
DECLARE
v_order_id INTEGER;
BEGIN
FOR v_order_id IN
SELECT order_id
FROM orders
WHERE order_status = 'Pending'
LOOP
BEGIN
PERFORM process_order(v_order_id);
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Sifariş işlənmədi %: %', v_order_id, SQLERRM;
END;
END LOOP;
END;
$$;
Bu funksiyanı çağıranda statusu Pending olan bütün sifarişlər işlənəcək və hər hansı error sadəcə loglanacaq.
Beləliklə, biz göstərdik ki, mürəkkəb prosedurları necə debug və optimizasiya etmək, onların etibarlılığını, performansını və oxunaqlılığını artırmaq olar. Bu biliklər real layihələrdə sənə çox lazım olacaq, çünki prosedurların keyfiyyəti tətbiqin uğurunu müəyyən edir.
GO TO FULL VERSION