Mundarija (33)
- Bu darsda
- 1. Nega bu kerak?
- 2. Trigger anatomiyasi
- 2.1 Ikki savol: qachon va necha marta
- 2.2 Bitta INSERT yo'li
- 2.3 Trigger funksiyasining maxsus o'zgaruvchilari
- 3. BEFORE ROW: qatorni yozishdan oldin
- 3.1 RETURN NEW va RETURN NULL
- 3.2 updated_at trigger'i va UPDATE OF tuzog'i
- 3.3 WHEN sharti
- 4. AFTER ROW: audit va bog'liq jadvallar
- 4.1 Audit trigger'i
- 4.2 Trigger ichidan o'z jadvaliga yozish — rekursiya
- 5. STATEMENT trigger va transition table
- 6. Narx: trigger'lar bepul emas
- 6.1 Kanon o'lchovi
- 6.2 Qaysi trigger qancha turadi
- 6.3 Yashirin mantiq: trigger'ni o'chirsangiz nima buziladi
- 7. Trigger yoki ilova kodi?
- 8. Ko'p uchraydigan xatolar
- 8.1 BEFORE trigger'da RETURN NEW ni unutish
- 8.2 AFTER trigger'da NEW ni o'zgartirish
- 8.3 UPDATE OF = "qiymat o'zgardi" deb o'ylash
- 8.4 Trigger ichida o'z jadvalini yangilash
- 8.5 Ommaviy yuklashda trigger'ni "shunchaki o'chirish"
- 9. Mashqlar
- 1-mashq (oson): updated_at
- 2-mashq (o'rta): Narx tarixi
- 3-mashq (qiyin): Statement-level audit
- 10. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi
- 11. Real ishda
- Xulosa
- Manbalar
PostgreSQL triggerlari: BEFORE/AFTER, ROW/STATEMENT, audit va yashirin narx
Qisqacha: Trigger — jadvalga INSERT, UPDATE yoki DELETE bo'lganda bazaning o'zi chaqiradigan funksiya. BEFORE ROW trigger qatorni yozishdan oldin o'zgartira (
NEW) yoki jim tashlab yubora oladi; AFTER trigger tayyor natijaga qarab boshqa jadvalga yozadi (audit, hisoblagich); STATEMENT trigger bitta buyruqqa bir marta ishlaydi. Trigger'lar bepul emas: kanonda 5 000 qatorli INSERT trigger'lar bilan o'n barobardan ko'proq sekin bo'lgan. Va ular ko'rinmaydi — kodni o'qigan odam ularni bilmasligi mumkin.
Bu darsda
- Trigger'ning to'rt turini (BEFORE/AFTER × ROW/STATEMENT) va qaysi biri nima uchunligini ajratasiz.
NEW,OLD,TG_OPva boshqaTG_o'zgaruvchilari bilan trigger funksiyasi yozasiz.UPDATE OFvaWHENshartlarining farqini bilasiz,updated_atva audit trigger'larini o'qiysiz.- Transition table bilan statement-level trigger yozasiz.
- Trigger'larning narxini o'lchaysiz va ommaviy yuklashda ular bilan qanday ishlashni bilasiz.
Oldin bilishingiz kerak: Funksiyalar va PL/pgSQL, Denormalizatsiya va hisoblangan maydonlar, Soft delete, timestamps va tarix jadvallari.
1. Nega bu kerak?
26-qismda vazifalar bazasiga beshta trigger qo'shdik: izohlar soni, teglar, yangilangan_vaqt, tarix va tartib. Ular ishladi, lekin biz ularni "qora quti" sifatida ishlatdik. Endi ochib ko'ramiz: ular qachon, qaysi tartibda chaqiriladi, nimani o'zgartira oladi va qancha turadi.
Oldingi darsda funksiya yozishni o'rgandik. Trigger — o'sha funksiyani jadval hodisasiga ulash. O'xshatish: do'kon eshigidagi sensor. Kim kirsa — xaridormi, kuryermi — sensor o'zi qo'ng'iroq chaladi va hisoblagichga +1 qo'shadi. Sotuvchi har mijozda "hisoblagichni bos" deb eslashi shart emas. Lekin sensor juda ko'p bo'lsa va har biri sekin bo'lsa — eshik oldida navbat yig'iladi.
Bu darsdagi so'rovlarni biz ishga tushirdik: PostgreSQL 17.6, kanon holat 04 sxemasi. Kanon (IlmHamroh, 2026-10, PostgreSQL 18.6) bilan chiqish bir xil; faqat vaqt o'lchovlari farq qiladi — ular alohida belgilangan. Hamma o'zgarish ROLLBACK bilan qaytariladi.
2. Trigger anatomiyasi
2.1 Ikki savol: qachon va necha marta
Har trigger ikki o'q bo'yicha tanlanadi:
| ROW (har qator uchun) | STATEMENT (buyruqqa bir marta) | |
|---|---|---|
| BEFORE | NEW ni o'zgartirish, qatorni tashlash |
buyruqdan oldin tekshiruv |
| AFTER | boshqa jadvalga yozish (audit, hisoblagich) | jamlangan ish (transition table bilan) |
Uchinchi vaqt — INSTEAD OF — faqat view uchun: "view'ga yozilganda o'rniga shuni qil". View va materialized view darsida avtomatik yangilanmaydigan view'ni ko'rgandik — INSTEAD OF trigger uni yoziladigan qila oladi.
Kanondagi trigger'lar ro'yxati pg_trigger katalogidan. tgtype — bitlar to'plami: 1-bit — ROW, 2-bit — BEFORE:
SELECT tgname,
CASE WHEN tgtype & 2 = 2 THEN 'BEFORE' ELSE 'AFTER' END
AS qachon,
CASE WHEN tgtype & 1 = 1 THEN 'ROW' ELSE 'STATEMENT' END
AS daraja,
tgfoid::regproc AS funksiya
FROM pg_trigger WHERE NOT tgisinternal
ORDER BY tgrelid::regclass::text, tgname; tgname | qachon | daraja | funksiya
---------------------------+--------+--------+--------------------------
izohlar_soni_trg | AFTER | ROW | izohlar_sonini_yangila
vazifa_tarixi_trg | AFTER | ROW | vazifa_tarixini_yoz
vazifa_teglari_trg | AFTER | ROW | vazifa_teglarini_yangila
vazifalar_tartib_trg | BEFORE | ROW | tartibni_qoy
vazifalar_yangilangan_trg | BEFORE | ROW | yangilangan_vaqtni_qoy
(5 rows)NOT tgisinternal — Postgres o'zi yaratgan ichki trigger'larni yashiradi. Ha, tashqi kalitlar (foreign key) ham ichkarida trigger bilan ishlaydi — buni narx bo'limida ko'rasiz.
2.2 Bitta INSERT yo'li
Bitta INSERT INTO vazifalar qatori qanday yo'ldan o'tadi? Diagrammada kanondagi trigger'lar joyiga qarang:
flowchart TD
A["INSERT buyrug'i"] --> B["BEFORE STATEMENT"]
B --> C["BEFORE ROW<br/>tartibni_qoy: NEW.tartib"]
C --> D["Cheklovlar: NOT NULL,<br/>CHECK, UNIQUE"]
D --> E["Qator yoziladi"]
E --> F["AFTER ROW: teglar,<br/>tarix, FK tekshiruvi"]
F --> G["AFTER STATEMENT"]
G --> H["Tranzaksiya: COMMIT"]Muhim tartib: BEFORE ROW trigger NOT NULL va CHECK dan oldin ishlaydi. Shuning uchun tartibni_qoy bo'sh tartib ni to'ldirib ulguradi va NOT NULL buzilmaydi. AFTER trigger'lar va asosiy buyruq — bitta tranzaksiyada: trigger xato bersa, INSERT ham bekor bo'ladi.
2.3 Trigger funksiyasining maxsus o'zgaruvchilari
Trigger funksiyasi RETURNS trigger deb e'lon qilinadi va argument olmaydi. Ma'lumotni maxsus o'zgaruvchilardan oladi:
| O'zgaruvchi | Ma'nosi |
|---|---|
NEW / OLD |
yangi / eski qator (ROW trigger'da) |
TG_OP |
'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE' |
TG_WHEN, TG_LEVEL |
BEFORE/AFTER, ROW/STATEMENT |
TG_NAME, TG_TABLE_NAME |
trigger va jadval nomi |
Bitta funksiyani ikki trigger'ga ulab, ularni ko'ramiz:
BEGIN;
CREATE TABLE t_m (id int, nom text);
CREATE FUNCTION t_kim() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
RAISE NOTICE '% % % % %', TG_NAME, TG_WHEN, TG_LEVEL, TG_OP,
TG_TABLE_NAME;
RETURN NULL;
END $$;
CREATE TRIGGER t_m_row AFTER INSERT ON t_m
FOR EACH ROW EXECUTE FUNCTION t_kim();
CREATE TRIGGER t_m_stmt AFTER INSERT ON t_m
FOR EACH STATEMENT EXECUTE FUNCTION t_kim();
INSERT INTO t_m VALUES (1, 'osh'), (2, 'manti');
ROLLBACK;BEGIN
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
CREATE TRIGGER
NOTICE: t_m_row AFTER ROW INSERT t_m
NOTICE: t_m_row AFTER ROW INSERT t_m
NOTICE: t_m_stmt AFTER STATEMENT INSERT t_m
INSERT 0 2
ROLLBACKIkki qator — ROW trigger ikki marta, STATEMENT trigger bir marta. Bitta funksiya bir nechta trigger'ga xizmat qilishi mumkin: TG_OP va TG_TABLE_NAME ga qarab o'zini boshqacha tutadi (kanondagi vazifa_tarixini_yoz aynan shunday).
Tekshirib ko'ring:
INSERT INTO t_m SELECT g, 'x' FROM generate_series(1, 1000) g;buyrug'idat_kimnecha marta chaqiriladi?
Javob
1 001 marta: ROW trigger har qator uchun (1 000) va STATEMENT trigger bir marta. Ommaviy yuklashda ROW trigger'lar asosiy narxni beradi — buni narx bo'limida o'lchaymiz.
3. BEFORE ROW: qatorni yozishdan oldin
3.1 RETURN NEW va RETURN NULL
BEFORE ROW trigger'ning qaytargan qiymati — yoziladigan qator:
RETURN NEW— qator yoziladi (trigger o'zgartirgan bo'lsa — o'zgargan holda);RETURN NULL— qator jim tashlab yuboriladi, xato yo'q.
Bitta hodisada bir nechta BEFORE trigger bo'lsa, ular nom bo'yicha alifbo tartibida ishlaydi va har biri oldingisining NEW ini oladi. Kanon tajribasi: avval b_ikkinchi, keyin a_birinchi yaratiladi, keyin manfiy id ni tashlaydigan c_tashla qo'shiladi:
BEGIN;
CREATE TABLE t_tr (id int, izoh text[] DEFAULT '{}');
CREATE FUNCTION t_yoz() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN NEW.izoh := NEW.izoh || TG_NAME::text; RETURN NEW; END $$;
CREATE TRIGGER b_ikkinchi BEFORE INSERT ON t_tr
FOR EACH ROW EXECUTE FUNCTION t_yoz();
CREATE TRIGGER a_birinchi BEFORE INSERT ON t_tr
FOR EACH ROW EXECUTE FUNCTION t_yoz();
INSERT INTO t_tr (id) VALUES (1) RETURNING izoh;
CREATE FUNCTION t_tashla() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF NEW.id < 0 THEN RETURN NULL; END IF;
RETURN NEW;
END $$;
CREATE TRIGGER c_tashla BEFORE INSERT ON t_tr
FOR EACH ROW EXECUTE FUNCTION t_tashla();
INSERT INTO t_tr (id) VALUES (-5);
SELECT count(*) FROM t_tr;
ROLLBACK;BEGIN
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
CREATE TRIGGER
izoh
-------------------------
{a_birinchi,b_ikkinchi}
(1 row)
INSERT 0 1
CREATE FUNCTION
CREATE TRIGGER
INSERT 0 0
count
-------
1
(1 row)
ROLLBACKIkki narsaga qarang:
{a_birinchi,b_ikkinchi}—b_ikkinchioldin yaratilgan, lekina_birinchioldin ishladi. Tartib — yaratilish vaqti emas, nom. Shuning uchun ba'zi jamoalar trigger nomiga raqam qo'yadi:10_tartib,20_vaqt.INSERT 0 0— xato yo'q, lekin qator ham yo'q (jadvalda faqat birinchi qator —count1). Ilova "muvaffaqiyatli" deb o'ylaydi. Bu BEFORE trigger'ning eng xavfli kuchi: ma'lumot jim yo'qoladi. Rad etish kerak bo'lsa —RAISE EXCEPTION,RETURN NULLemas.
3.2 updated_at trigger'i va UPDATE OF tuzog'i
Kanondagi yangilangan_vaqtni_qoy (26-qismdagi 013 migratsiyasi):
CREATE OR REPLACE FUNCTION yangilangan_vaqtni_qoy() RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
NEW.yangilangan_vaqt := date_trunc('milliseconds', now());
NEW.versiya := OLD.versiya + 1;
RETURN NEW;
END $$;
CREATE TRIGGER vazifalar_yangilangan_trg
BEFORE UPDATE OF matn, bajarildi, ustuvorlik, muddat, ota_id,
ochirilgan_vaqt, tartib ON vazifalar
FOR EACH ROW EXECUTE FUNCTION yangilangan_vaqtni_qoy();UPDATE OF matn, bajarildi, … — trigger faqat shu ustunlar SET ro'yxatida bo'lsa ishlaydi. izohlar_soni o'zgarganda (izoh qo'shilganda) versiya oshmaydi — to'g'ri, bu tahrir emas.
Lekin kanon bitta tuzoqni topdi. UPDATE OF qiymat o'zgarishini emas, ustun tilga olinganini tekshiradi:
BEGIN;
UPDATE vazifalar SET bajarildi = bajarildi WHERE id = 1
RETURNING versiya AS versiya_oshdi;
ROLLBACK;BEGIN
versiya_oshdi
---------------
2
(1 row)
UPDATE 1
ROLLBACKHech narsa o'zgarmadi, versiya esa 1 dan 2 ga oshdi. API "bajarildi = false" ni ikki marta yuborsa — ikki marta yangi versiya, ikki marta yangi ETag, mijoz keshi bekor bo'ladi.
3.3 WHEN sharti
Yechim — trigger e'lonidagi WHEN sharti. U funksiyani chaqirishdan oldin tekshiriladi: shart yolg'on bo'lsa, funksiya umuman chaqirilmaydi (arzon). Kichik jadvalda ko'ramiz:
BEGIN;
CREATE TABLE t_taom (id int PRIMARY KEY, narx int,
versiya int NOT NULL DEFAULT 1);
CREATE FUNCTION t_versiya() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NEW.versiya := OLD.versiya + 1;
RETURN NEW;
END $$;
CREATE TRIGGER t_taom_versiya BEFORE UPDATE ON t_taom
FOR EACH ROW
WHEN (OLD.narx IS DISTINCT FROM NEW.narx)
EXECUTE FUNCTION t_versiya();
INSERT INTO t_taom VALUES (1, 35000);
UPDATE t_taom SET narx = 35000;
UPDATE t_taom SET narx = 36000;
SELECT * FROM t_taom;
ROLLBACK;BEGIN
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
INSERT 0 1
UPDATE 1
UPDATE 1
id | narx | versiya
----+-------+---------
1 | 36000 | 2
(1 row)
ROLLBACKIkki UPDATE, lekin versiya bir marta oshdi — 35 000 → 35 000 o'zgarish emas. IS DISTINCT FROM — NULL'ni ham to'g'ri solishtiradigan "teng emas" (NULL mantiqi): NULL <> NULL — NULL, NULL IS DISTINCT FROM NULL — false.
Butun qator uchun: WHEN (OLD.* IS DISTINCT FROM NEW.*) — UPDATE OF tuzog'ining tavsiya etilgan yechimi. Kanon trigger'i hozircha UPDATE OF bilan qolgan (26-qismdagi 013 qo'llangan, o'zgartirilmaydi). O'z loyihangizda bu shartni yangi migratsiya fayli bilan qo'shasiz: trigger'ni DROP TRIGGER va WHEN bilan qayta CREATE TRIGGER.
Tekshirib ko'ring:
WHENichida negaOLDINSERT trigger'ida ishlatib bo'lmaydi?
Javob
INSERT'da eski qator yo'q — OLD mavjud emas. Postgres buni trigger yaratishdayoq rad etadi (INSERT trigger's WHEN condition cannot reference OLD values). Xuddi shunday DELETE'da NEW yo'q. Shuning uchun WHEN (OLD … NEW) faqat BEFORE/AFTER UPDATE uchun.
4. AFTER ROW: audit va bog'liq jadvallar
4.1 Audit trigger'i
AFTER trigger qator yozilgandan keyin ishlaydi — NEW endi o'zgarmaydi, qaytgan qiymat e'tiborsiz (RETURN NULL). U boshqa jadvalga yozish uchun: hisoblagich, teglar, tarix. Kanondagi tarix funksiyasi (26-qism, 010):
CREATE FUNCTION vazifa_tarixini_yoz() RETURNS trigger
LANGUAGE plpgsql AS $$
DECLARE
kim bigint := nullif(
current_setting('ilova.foydalanuvchi_id', true), '')::bigint;
BEGIN
INSERT INTO vazifa_tarixi (vazifa_id, amal, eski, yangi, kim_id)
VALUES (
coalesce(NEW.id, OLD.id), TG_OP,
CASE WHEN TG_OP <> 'INSERT'
THEN to_jsonb(OLD) - 'izohlar_soni' END,
CASE WHEN TG_OP <> 'DELETE'
THEN to_jsonb(NEW) - 'izohlar_soni' END,
kim);
RETURN NULL;
END $$;Bitta funksiya uch hodisaga: coalesce(NEW.id, OLD.id) — DELETE'da NEW NULL, INSERT'da OLD NULL. to_jsonb(OLD) — butun qatorni JSONB'ga aylantiradi (JSONB darsida o'rgangan - operatori bilan keraksiz kalitni olib tashlaydi). "Kim" — ilova tranzaksiyada qo'yadigan sozlamadan (Soft delete, timestamps va tarix jadvallari darsidagi SET LOCAL).
Amalda:
BEGIN;
SET LOCAL ilova.foydalanuvchi_id = '1';
UPDATE vazifalar SET matn = 'Qoralama yozish va tekshirish'
WHERE id = 44;
SELECT amal, eski->>'matn' AS eski, yangi->>'matn' AS yangi,
kim_id
FROM vazifa_tarixi WHERE vazifa_id = 44
ORDER BY id DESC LIMIT 1;
ROLLBACK;BEGIN
SET
UPDATE 1
amal | eski | yangi | kim_id
--------+-----------------+-------------------------------+--------
UPDATE | Qoralama yozish | Qoralama yozish va tekshirish | 1
(1 row)
ROLLBACKIlova kodida "tarixga yoz" degan qator yo'q — lekin tarix yozildi. Bu audit trigger'ining asosiy afzalligi: psql'dan qo'lda qilingan tahrir ham, unutilgan admin skript ham iz qoldiradi.
4.2 Trigger ichidan o'z jadvaliga yozish — rekursiya
AFTER UPDATE trigger o'sha jadvalni yana UPDATE qilsa nima bo'ladi? Har UPDATE trigger'ni qayta chaqiradi:
CREATE TABLE t_r (id int, n int);
CREATE FUNCTION t_oshir() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
UPDATE t_r SET n = n + 1 WHERE id = NEW.id;
RETURN NULL;
END $$;
CREATE TRIGGER t_r_trg AFTER UPDATE ON t_r
FOR EACH ROW EXECUTE FUNCTION t_oshir();
INSERT INTO t_r VALUES (1, 0);
UPDATE t_r SET n = 1;Biz ishga tushirdik (PostgreSQL 17.6; CONTEXT juftligi yuzlab marta takrorlanadi, qisqartirdik):
ERROR: stack depth limit exceeded
HINT: Increase the configuration parameter "max_stack_depth" (currently 2048kB), after ensuring the platform's stack depth limit is adequate.
CONTEXT: SQL statement "UPDATE t_r SET n = n + 1 WHERE id = NEW.id"
PL/pgSQL function t_oshir() line 3 at SQL statement
…Tarjimasi: "stek chuqurligi chegarasidan oshib ketdi". HINT ning maslahatiga (max_stack_depth ni oshirish) amal qilmang — muammo cheksiz rekursiyada, stekda emas. Yechimlar: BEFORE trigger'da NEW ni o'zgartirish (qo'shimcha UPDATE'siz), WHEN sharti yoki pg_trigger_depth() — hozirgi trigger chuqurligi:
BEGIN;
CREATE TABLE t_r (id int, n int);
CREATE FUNCTION t_oshir() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF pg_trigger_depth() < 3 THEN
UPDATE t_r SET n = n + 1 WHERE id = NEW.id;
END IF;
RETURN NULL;
END $$;
CREATE TRIGGER t_r_trg AFTER UPDATE ON t_r
FOR EACH ROW EXECUTE FUNCTION t_oshir();
INSERT INTO t_r VALUES (1, 0);
UPDATE t_r SET n = 1;
SELECT n FROM t_r;
ROLLBACK;BEGIN
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
INSERT 0 1
UPDATE 1
n
---
3
(1 row)
ROLLBACKn = 1 (bizning UPDATE) → 1-chuqurlikda +1 → 2-chuqurlikda +1 → 3-chuqurlikda to'xtadi. pg_trigger_depth() — favqulodda tormoz; yaxshi dizaynda trigger o'z jadvaliga qayta yozmaydi.
Tekshirib ko'ring:
izohlar_sonini_yangilatrigger'iizohlarga emas,vazifalarga yozadi.vazifalarda esavazifa_tarixi_trgbor. Izoh qo'shilganda tarixga yozuv tushadimi?
Javob
Yo'q. vazifa_tarixi_trg INSERT va DELETE'dan tashqari faqat UPDATE OF matn, bajarildi, ustuvorlik, muddat, ota_id, ochirilgan_vaqt da ishlaydi. Izoh trigger'i SET izohlar_soni = … qiladi — bu ustun ro'yxatda yo'q. UPDATE OF bu yerda foydali: hisoblagich o'zgarishi tarixni to'ldirmaydi (yana to_jsonb(…) - 'izohlar_soni' ham shuning uchun).
5. STATEMENT trigger va transition table
ROW trigger 13 qator uchun 13 marta chaqiriladi. Ba'zan bitta jamlangan ish kerak: "bu buyruq nechta qatorni o'zgartirdi?". Transition table (o'tish jadvali) — statement trigger ichida buyruq ta'sir qilgan barcha qatorlar to'plami (REFERENCING NEW TABLE AS … / OLD TABLE AS …). Kanon tajribasi:
BEGIN;
CREATE TABLE t_hisob (amal text, soni int,
vaqt timestamptz DEFAULT now());
CREATE FUNCTION t_stmt() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
INSERT INTO t_hisob (amal, soni)
SELECT TG_OP, count(*) FROM yangi;
RETURN NULL;
END $$;
CREATE TRIGGER t_stmt_trg AFTER UPDATE ON vazifalar
REFERENCING NEW TABLE AS yangi
FOR EACH STATEMENT EXECUTE FUNCTION t_stmt();
UPDATE vazifalar SET ustuvorlik = 'yuqori' WHERE egasi_id = 1;
UPDATE vazifalar SET ustuvorlik = 'past' WHERE false;
SELECT amal, soni FROM t_hisob;
ROLLBACK;BEGIN
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
UPDATE 13
UPDATE 0
amal | soni
--------+------
UPDATE | 13
UPDATE | 0
(2 rows)
ROLLBACKIkki xulosa:
- 13 qator — bitta chaqiruv. Ommaviy amalda ROW trigger'dan ancha arzon: audit'ni
INSERT INTO tarix SELECT … FROM yangibilan bitta buyruqda yozish mumkin. - 0 qatorli UPDATE'da ham statement trigger ishladi. "Hech narsa o'zgarmasa, trigger chaqirilmaydi" deb o'ylamang — kerak bo'lsa ichida
IF NOT EXISTS (SELECT 1 FROM yangi)tekshiring.
6. Narx: trigger'lar bepul emas
6.1 Kanon o'lchovi
5 000 vazifani bitta INSERT … SELECT bilan qo'shish. Ikki variant:
- trigger'lar bilan — oddiy rejim;
- trigger'larsiz —
SET LOCAL session_replication_role = replica. Bu sozlama replikatsiya uchun mo'ljallangan: oddiy trigger'lar va FK trigger'lari ishlamaydi. Faqat superuser qo'ya oladi.tartibni o'zimiz beramiz (pastda nega).
Kanon o'lchovida (IlmHamroh, 2026-10, PostgreSQL 18.6, psql \timing): trigger'lar bilan 3 350 ms, trigger'larsiz 118 ms. Biz o'z PostgreSQL 17.6 klasterimizda xuddi shu ikki buyruqni bajardik: 7 028 ms va 512 ms. Ikkala mashina ham o'lchov paytida parallel ishlar bilan band edi — kanonning boshqa urinishlarida trigger'li INSERT 1 082 ms dan 4 680 ms gacha tarqalgan. Shuning uchun mutlaq raqamni emas, nisbatni eslang: trigger'lar bu INSERT'ni o'n barobardan ko'proq sekinlashtirdi (kanonda 28, bizda 14 barobar).
6.2 Qaysi trigger qancha turadi
EXPLAIN ANALYZE har trigger'ga sarflangan vaqtni alohida ko'rsatadi (EXPLAIN ANALYZE'ni o'qish):
BEGIN;
EXPLAIN (ANALYZE, COSTS OFF) INSERT INTO vazifalar (egasi_id, matn)
SELECT 1 + g % 7, 'Ommaviy vazifa ' || g || ' #ish'
FROM generate_series(1, 5000) g;
ROLLBACK;Biz ishga tushirdik (PostgreSQL 17.6, band mashina — parallel ishlar bor edi, bitta o'lchov; vaqt sizda boshqacha bo'ladi):
Insert on vazifalar (actual time=2893.816..2893.816 rows=0 loops=1)
-> Function Scan on generate_series g (actual time=0.986..46.583 rows=5000 loops=1)
Planning Time: 1.111 ms
Trigger for constraint vazifalar_egasi_id_fkey: time=152.622 calls=5000
Trigger for constraint vazifalar_ota_id_fkey: time=8.499 calls=5000
Trigger vazifa_tarixi_trg: time=605.508 calls=5000
Trigger vazifa_teglari_trg: time=3842.246 calls=5000
Trigger vazifalar_tartib_trg: time=2414.537 calls=5000
Execution Time: 7511.223 msGrafikda xuddi shu raqamlar (millisekund, 5 000 qator):
- teglarhar qatorda matnni ajratib, teg yozadi3 842 ms
- tartibhar qatorda max(tartib)2 415 ms
- tarixhar qatorda bitta INSERT606 ms
- FK egasi153 ms
- FK ota8 ms
Manba: IlmHamroh o'lchovi, 2026-10-25: PostgreSQL 17.6, Windows 11, holat 04, EXPLAIN ANALYZE, band mashina, bitta o'lchov
Nimaga qarash kerak:
calls=5000— har ROW trigger har qator uchun alohida chaqirildi.- Eng qimmati — teglar trigger'i: u har qatorda matnni ajratadi va bir nechta jadvalga yozadi. Ikkinchisi —
tartibni_qoy: har qator uchun alohidaSELECT max(tartib). - Tashqi kalitlar ham trigger (
Trigger for constraint …) — ichki, lekin bepul emas.
6.3 Yashirin mantiq: trigger'ni o'chirsangiz nima buziladi
session_replication_role = replica bilan "tez yuklaymiz" degan fikr tug'iladi. Lekin trigger'lar faqat sekinlashtirmaydi — ular ma'lumotni to'ldiradi. Kanon tajribasi:
BEGIN;
SET LOCAL session_replication_role = replica;
INSERT INTO vazifalar (egasi_id, matn)
VALUES (1, 'Trigger''siz vazifa');
ROLLBACK;Kanon chiqishi (PostgreSQL 18.6; DETAIL dagi id va vaqt har safar boshqacha):
BEGIN
SET
ERROR: null value in column "tartib" of relation "vazifalar" violates not-null constraint
DETAIL: Failing row contains (15053, 1, Trigger'siz vazifa, f, 2026-10-24 14:46:34.086+00, orta, null, null, null, 0, 2026-10-24 14:46:34.086+00, null, 1, null).
ROLLBACKtartib ni tartibni_qoy trigger'i to'ldirardi — u o'chdi va NOT NULL ishga tushdi. Bu yerda baxtimiz bor: xato chiqdi. Agar tartib NULL'ga ruxsat berganida — vazifalar jim, tartibsiz yozilardi; teglar va tarix esa umuman yozilmasdi. Bu — yashirin mantiq xavfi amalda: trigger'ni bilmagan dasturchi uni "tezlik uchun" o'chirib, ma'lumotni buzadi.
Ommaviy yuklashning to'g'ri yo'llari:
- Statement trigger + transition table (bitta chaqiruv).
- Yuklashdan keyin bir martalik to'ldirish:
UPDATE … FROMbilan teglar va hisoblagichlarni qayta hisoblash (Denormalizatsiya va hisoblangan maydonlar darsidagi backfill). - Aniq trigger'ni o'chirish:
ALTER TABLE vazifalar DISABLE TRIGGER vazifa_teglari_trg;— lekin bu jadvalni qattiq qulflaydi va boshqa sessiyalarga ham ta'sir qiladi. Tranzaksiya ichida qilib, oxiridaENABLEqilish shart.
Tekshirib ko'ring: Nega
ALTER TABLE … DISABLE TRIGGERishlayotgan saytda xavfli,SET LOCAL session_replication_roleesa unchalik emas?
Javob
DISABLE TRIGGER — jadval sozlamasi: u COMMIT bo'lgach hamma ulanishlar uchun trigger'siz ishlaydi, ustiga ALTER TABLE qulfi (SHARE ROW EXCLUSIVE) yozuvchilarni to'xtatadi. SET LOCAL session_replication_role faqat shu tranzaksiyaga ta'sir qiladi va oxirida o'zi qaytadi. Ikkalasida ham trigger bajaradigan mantiqni o'zingiz qilishingiz kerak.
7. Trigger yoki ilova kodi?
| Trigger'da | Ilovada |
|---|---|
Har qanday yo'l bilan yozilganda ham bajarilishi shart (audit, updated_at) |
Tashqi tizimga murojaat (email, HTTP, navbat) |
Hisoblangan qiymat bir jadval ichida (tartib, hisoblagich) |
Murakkab, tez-tez o'zgaradigan biznes qoida |
| Ma'lumot yaxlitligi, cheklov bilan ifodalab bo'lmaydigan | Foydalanuvchiga ko'rinadigan xabar va tekshiruv |
Va eng muhim qoida: trigger'larni hujjatlang. Sxemada izoh (COMMENT ON TRIGGER), README'da ro'yxat. Yangi dasturchi "nega versiya o'zi oshyapti?" deb bir kun yo'qotmasin.
Ikki qo'shimcha turni bilib qo'ying (bu kursda chuqur ishlatmaymiz):
- Constraint trigger (
CREATE CONSTRAINT TRIGGER … DEFERRABLE INITIALLY DEFERRED) — tekshiruvni COMMIT'gacha kechiktiradi, Ilg'or constraint'lar va generated ustunlar darsidagi deferrable cheklov kabi. - Event trigger — jadval emas, DDL hodisasiga (
CREATE TABLE,DROP) ulanadi; masalan "prod'daDROP TABLEni taqiqlash". Faqat superuser yaratadi.
8. Ko'p uchraydigan xatolar
8.1 BEFORE trigger'da RETURN NEW ni unutish
Funksiya RETURN siz tugasa — control reached end of trigger procedure without RETURN xatosi. RETURN NULL yozilsa — qator jim yo'qoladi (INSERT 0 0). BEFORE ROW — doim RETURN NEW (yoki ataylab NULL, izoh bilan).
8.2 AFTER trigger'da NEW ni o'zgartirish
AFTER trigger'da NEW.ustun := … hech narsa qilmaydi — qator allaqachon yozilgan. Qatorni o'zgartirish — faqat BEFORE'da.
8.3 UPDATE OF = "qiymat o'zgardi" deb o'ylash
U faqat ustun SET da borligini tekshiradi. WHEN (OLD.x IS DISTINCT FROM NEW.x).
8.4 Trigger ichida o'z jadvalini yangilash
stack depth limit exceeded. BEFORE trigger'da NEW orqali yoki WHEN bilan.
8.5 Ommaviy yuklashda trigger'ni "shunchaki o'chirish"
Yashirin mantiq yo'qoladi (kanon: tartib NULL). Trigger nimani to'ldirishini o'qing va keyin o'zingiz to'ldiring yoki statement trigger'ga o'ting.
9. Mashqlar
Mashqlar kurs/mashqlar/27/06-triggerlar/ da. Ular o'z jadvallaringiz bilan, istalgan bo'sh bazada bajariladi; yechimlar tekshirilgan (biz ishga tushirdik, PostgreSQL 17.6).
1-mashq (oson): updated_at
m06_taom (id int PRIMARY KEY, nom text, narx int, yangilangan timestamptz NOT NULL DEFAULT '2026-01-01') jadvaliga BEFORE UPDATE trigger yozing: yangilangan ni '2026-10-25' ga qo'ysin (darsda now() bo'lardi — mashqda natija barqaror bo'lishi uchun aniq sana). Osh qo'shing, narxini yangilang va nom || ' ' || yangilangan::date ni chiqaring.
Yechim
CREATE TABLE m06_taom (id int PRIMARY KEY, nom text, narx int,
yangilangan timestamptz NOT NULL DEFAULT '2026-01-01');
CREATE FUNCTION m06_vaqt() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NEW.yangilangan := '2026-10-25';
RETURN NEW;
END $$;
CREATE TRIGGER m06_taom_vaqt BEFORE UPDATE ON m06_taom
FOR EACH ROW EXECUTE FUNCTION m06_vaqt();
INSERT INTO m06_taom VALUES (1, 'osh', 35000);
SELECT nom || ' ' || yangilangan::date FROM m06_taom;
UPDATE m06_taom SET narx = 36000 WHERE id = 1;
SELECT nom || ' ' || yangilangan::date FROM m06_taom;
DROP TABLE m06_taom;
DROP FUNCTION m06_vaqt;Biz ishga tushirdik (psql -X -q -A -t, PostgreSQL 17.6):
osh 2026-01-01
osh 2026-10-25INSERT'da trigger ishlamadi (BEFORE UPDATE) — DEFAULT qoldi; UPDATE'da sana trigger'dan keldi. Ilova yangilangan ni yubormadi ham.
2-mashq (o'rta): Narx tarixi
m06_mahsulot (id int PRIMARY KEY, nom text, narx int) va m06_narx_tarixi (mahsulot_id int, eski int, yangi int) yarating. AFTER UPDATE trigger faqat narx haqiqatan o'zgarganda tarixga yozsin (WHEN). Manti narxini 30 000 → 30 000 → 32 000 → 31 000 qilib yangilang va tarixni chiqaring.
Yechim
CREATE TABLE m06_mahsulot (id int PRIMARY KEY, nom text, narx int);
CREATE TABLE m06_narx_tarixi (mahsulot_id int, eski int, yangi int);
CREATE FUNCTION m06_narx_yoz() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
INSERT INTO m06_narx_tarixi VALUES (NEW.id, OLD.narx, NEW.narx);
RETURN NULL;
END $$;
CREATE TRIGGER m06_narx_trg AFTER UPDATE OF narx ON m06_mahsulot
FOR EACH ROW
WHEN (OLD.narx IS DISTINCT FROM NEW.narx)
EXECUTE FUNCTION m06_narx_yoz();
INSERT INTO m06_mahsulot VALUES (1, 'manti', 30000);
UPDATE m06_mahsulot SET narx = 30000 WHERE id = 1;
UPDATE m06_mahsulot SET narx = 32000 WHERE id = 1;
UPDATE m06_mahsulot SET narx = 31000 WHERE id = 1;
SELECT eski || ' -> ' || yangi FROM m06_narx_tarixi;
DROP TABLE m06_mahsulot, m06_narx_tarixi;
DROP FUNCTION m06_narx_yoz;Biz ishga tushirdik (psql -X -q -A -t, PostgreSQL 17.6):
30000 -> 32000
32000 -> 31000Uch UPDATE — ikki yozuv: birinchisi qiymatni o'zgartirmadi va WHEN funksiyani chaqirmadi. UPDATE OF narx va WHEN birga: biri "narx tilga olindimi", ikkinchisi "narx o'zgardimi".
3-mashq (qiyin): Statement-level audit
m06_buyurtma (id int PRIMARY KEY, holat text) va m06_audit (amal text, soni int) yarating. Bitta statement trigger (transition table bilan) INSERT va DELETE uchun ta'sir qilgan qatorlar sonini yozsin. Ishora: INSERT va DELETE uchun ikki alohida trigger kerak — REFERENCING NEW TABLE faqat INSERT/UPDATE'da, OLD TABLE — DELETE/UPDATE'da; funksiya bitta bo'lishi mumkin, TG_OP ga qarab yangi yoki eski dan sanaydi. 5 ta buyurtma qo'shing, 2 tasini o'chiring, 0 tasini o'chiring (WHERE false).
Yechim
CREATE TABLE m06_buyurtma (id int PRIMARY KEY, holat text);
CREATE TABLE m06_audit (amal text, soni int);
CREATE FUNCTION m06_sana() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO m06_audit SELECT TG_OP, count(*) FROM yangi;
ELSE
INSERT INTO m06_audit SELECT TG_OP, count(*) FROM eski;
END IF;
RETURN NULL;
END $$;
CREATE TRIGGER m06_ins AFTER INSERT ON m06_buyurtma
REFERENCING NEW TABLE AS yangi
FOR EACH STATEMENT EXECUTE FUNCTION m06_sana();
CREATE TRIGGER m06_del AFTER DELETE ON m06_buyurtma
REFERENCING OLD TABLE AS eski
FOR EACH STATEMENT EXECUTE FUNCTION m06_sana();
INSERT INTO m06_buyurtma
SELECT g, 'yangi' FROM generate_series(1, 5) g;
DELETE FROM m06_buyurtma WHERE id <= 2;
DELETE FROM m06_buyurtma WHERE false;
SELECT amal || ': ' || soni FROM m06_audit;
DROP TABLE m06_buyurtma, m06_audit;
DROP FUNCTION m06_sana;Biz ishga tushirdik (psql -X -q -A -t, PostgreSQL 17.6):
INSERT: 5
DELETE: 2
DELETE: 0Uch buyruq — uch yozuv, 5 qator uchun bitta chaqiruv. Uchinchi qator darsdagi topilmani takrorlaydi: 0 qatorli buyruqda ham statement trigger ishlaydi.
10. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi
Bu darsda vazifalar sxemasiga yangi migratsiya yo'q — beshta trigger 26-qismdan beri ishlab turibdi. Darsning loyihaga beradigan foydasi — ular haqidagi bilim:
vazifalar_yangilangan_trgUPDATE OFbilan ishlaydi va qiymat o'zgarmasa ham versiyani oshiradi. Tavsiya etilgan yechim —WHEN (OLD.* IS DISTINCT FROM NEW.*); kanon trigger'i hozirchaUPDATE OFbilan qoldi va bu tuzoqni biladi.- Ommaviy yuklash (Seeding va test ma'lumotlari) trigger'lar bilan sekin — bu kutilgan narx.
tartib, teglar va tarix kerak bo'lgani uchun trigger'lar o'chirilmaydi. - Kanondagi katta to'plam skripti (500 000 vazifa) trigger'larni o'chirib yuklagan va aynan
tartibNOT NULL xatosiga uchragan —tartibni qo'lda bergandan keyingina ishladi. Yuqoridagi "yashirin mantiq" bo'limi — o'sha voqea.
11. Real ishda
- Audit — moliya, tibbiyot, davlat tizimlarida majburiy talab: "kim, qachon, nimani o'zgartirdi". Ko'pincha aynan trigger + JSONB bilan quriladi (yoki
pgauditkengaytmasi bilan — kengaytmalar keyingi darslarda). - ORM va trigger: Prisma kabi vositalar trigger'ni bilmaydi —
updatedAtni ilovada qo'yadi. Ikkalasi birga ishlasa, qaysi biri ustun ekanini aniqlang (Prisma: modellar va atributlar). - Migratsiyalarda trigger qo'shish/o'zgartirish — qulf oladi; katta jadvalda texnik oynada bajariladi.
- Intervyu: "BEFORE va AFTER trigger farqi?", "Trigger'larning kamchiligi?", "Statement va row trigger qachon?" — ko'p uchraydi.
Xulosa
- Trigger = funksiya + hodisa: BEFORE/AFTER × ROW/STATEMENT (+ view uchun INSTEAD OF).
- BEFORE ROW
NEWni o'zgartiradi;RETURN NULLqatorni jim tashlaydi. Bir hodisadagi trigger'lar nom bo'yicha alifbo tartibida. UPDATE OF— ustun tilga olindimi;WHEN (OLD.x IS DISTINCT FROM NEW.x)— qiymat o'zgardimi.- Statement trigger + transition table — ommaviy amalda bitta chaqiruv, 0 qatorda ham ishlaydi.
- Trigger'lar narxi real: kanonda 5 000 qatorli INSERT o'n barobardan ko'proq sekin; FK ham ichki trigger.
- Trigger'ni o'chirish yashirin mantiqni o'chiradi — hujjatlang va o'rnini to'ldiring.
Keyingi dars: Postgres'da full-text search — generated tsvector ustun, GIN indeks va o'zbekcha matn (apostrof, kirill) bilan qidiruv.
Manbalar
- PostgreSQL 18 hujjati: Triggers — Overview of Trigger Behavior — 2026-10-25 da ko'rildi.
- PostgreSQL 18 hujjati: CREATE TRIGGER (
WHEN,REFERENCING, nom tartibi). - PostgreSQL 18 hujjati: Trigger Functions (PL/pgSQL), Event Triggers.
- PostgreSQL 18 hujjati: session_replication_role.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!