24 β Trigger va Event (chuqur)¶
β¬ οΈ Oldingi: 23 β Stored Procedure va Function Β· π README Β· Keyingi: 25 β Cursor β‘οΈ
Bu bobda: ma'lumotlar bazasining ikkita "avtomat" mexanizmini chuqur o'rganamiz. Trigger β jadvalga qo'yilgan soqchi: har
INSERT/UPDATE/DELETEda o'zi ishga tushib, ma'lumotni tekshiradi, to'g'rilaydi yoki log yozadi. Event β bazadagi budilnik: belgilangan vaqtda yoki har N daqiqada o'zi uyg'onib SQL bajaradi (eski log tozalash, kunlik hisobot yig'ish, arxivlash). Triggerning 6 turi (BEFORE/AFTER Γ INSERT/UPDATE/DELETE),OLDvaNEWpsevdo-jadvallari, bir hodisaga bir nechta trigger va ularning tartibi (FOLLOWS/PRECEDES), triggerning og'ir cheklovlari (o'z jadvalini o'zgartira olmasligi β 1442-xato,COMMIT/ROLLBACKtaqiqi), audit/tarix naqshi, hamdaevent_schedulerni yoqish,CREATE EVENT,ON SCHEDULE,ON COMPLETION PRESERVEvaALTER EVENTβ barchasini real misollar va tuzoqlar bilan ko'rib chiqamiz.
Trigger nima: jadvalga qo'yilgan soqchi¶
Tasavvur qiling, kutubxonada qoida bor: har bir kitob ijaraga berilganda, o'sha kitobning bo'sh nusxalar soni avtomatik bittaga kamayishi kerak. Buni ilovangizning har bir joyida qo'lda yozsangiz β bitta INSERT INTO ijaralar, keyin alohida UPDATE kitoblar SET nusxa_soni = nusxa_soni - 1 β ertaga kimdir UPDATEni unutib, bazada nomuvofiqlik paydo bo'ladi. Yechim: bu qoidani jadvalning o'ziga biriktirish. Shunda kim qaerdan INSERT qilmasin β ilovadanmi, mysql klientidanmi, boshqa procedure'danmi β qoida hamisha ishlaydi.
Ana shu "jadval ustiga osilgan, hodisaga avtomatik javob beradigan kod" β trigger (qo'zg'atuvchi). Trigger β bu siz chaqirmaydigan stored program. Uni hech kim CALL qilmaydi; u hodisa (INSERT, UPDATE, DELETE) ro'y berganda MySQL'ning o'zi ishga tushiradi.
π Triggerni eshik oldidagi soqchiga o'xshating: har kim kirsa-chiqsa, soqchi avtomatik tekshiradi β siz uni "chaqirmaysiz", u shunchaki o'sha eshikka biriktirilgan. Trigger ham aniq bir jadvalning aniq bir hodisasiga biriktiriladi.
Trigger ikki narsaga bog'langan bo'ladi:
- Qaysi jadval β trigger faqat bitta jadvalga tegishli.
- Qaysi hodisa va qachon β
INSERT/UPDATE/DELETEdan biri, va u oldin (BEFORE) yoki keyin (AFTER) ishga tushadi.
Eng sodda trigger sintaksisi:
USE kutubxona;
DELIMITER //
CREATE TRIGGER trg_misol
BEFORE INSERT ON ijaralar
FOR EACH ROW
BEGIN
-- har bir qo'shilayotgan qator uchun bajariladigan kod
SET NEW.olingan_sana = COALESCE(NEW.olingan_sana, CURDATE());
END //
DELIMITER ;
FOR EACH ROW β MySQL'da triggerlar har bir qator uchun ishlaydi (qator darajasidagi trigger). Agar bitta UPDATE 50 qatorni o'zgartirsa, trigger 50 marta ishga tushadi. MySQL'da boshqa variant yo'q β FOR EACH ROW doim yoziladi.
π‘ DELIMITER // nega kerak? Trigger tanasi (BEGIN ... END) ichida ko'p ; bor. Agar ajratuvchi ; bo'lib qolsa, MySQL birinchi ; da buyruq tugadi deb o'ylaydi. Shuning uchun vaqtincha ajratuvchini // ga o'zgartirib, butun CREATE TRIGGERni bitta birlik sifatida yuboramiz, so'ng DELIMITER ; bilan oddiy ; ga qaytamiz. Bu odat barcha stored program'larda (procedure, function, trigger, event) bir xil.
6 xil trigger: BEFORE/AFTER Γ INSERT/UPDATE/DELETE¶
Vaqt (BEFORE/AFTER) va hodisa (INSERT/UPDATE/DELETE) kombinatsiyasi 6 ta triggerni beradi. Bitta jadvalda har biridan kamida bittadan bo'lishi mumkin (MySQL 8'da bir xil turdan bir nechta ham mumkin β quyida ko'ramiz).
| Tur | Qachon ishlaydi | Asosiy vazifasi |
|---|---|---|
BEFORE INSERT |
Yangi qator yozilishidan oldin | Validatsiya, standart/hisoblangan qiymat berish (SET NEW.x = ...) |
AFTER INSERT |
Yangi qator yozilgandan keyin | Audit log, boshqa jadvalni yangilash (kaskad) |
BEFORE UPDATE |
Qator o'zgartirilishidan oldin | Yangi qiymatni tekshirish/tuzatish, o'zgarishni taqiqlash |
AFTER UPDATE |
Qator o'zgartirilgandan keyin | Eskiβyangi tarixni log jadvaliga yozish |
BEFORE DELETE |
Qator o'chirilishidan oldin | O'chirishni taqiqlash, bog'liq tekshiruv |
AFTER DELETE |
Qator o'chirilgandan keyin | Arxivga ko'chirish, audit log |
Qoida soddagina: BEFORE β qiymatni o'zgartirish yoki rad etish uchun (chunki qator hali yozilmagan, NEWni tahrirlash mumkin); AFTER β fakt ro'y bergach, izini boshqa joyga yozish uchun (audit, kaskad).
β οΈ Bu kitobda biz AFTER INSERT ichida "boshqa jadvalni yangilash" misollarini ko'rsatamiz, lekin yodda tuting: triggerlar yashirin ishlaydi va zanjir hosil qilishi mumkin (A jadval triggeri B'ni o'zgartiradi, B'ning triggeri C'ni...). Bu "ko'rinmas sehr" haqida bob oxirida alohida ogohlantiramiz.
OLD va NEW: triggerning ikki ko'zgusi¶
Trigger ichida ikkita maxsus psevdo-jadval bor: OLD va NEW. Ular orqali qatorning eski va yangi holatiga murojaat qilasiz.
NEW.ustunβ qatorning yangi qiymati (INSERT/UPDATEda kelayotgan ma'lumot).OLD.ustunβ qatorning eski qiymati (UPDATE/DELETEdan oldingi ma'lumot).
Qaysi hodisada qaysi biri mavjudligini quyidagi jadval aniq ko'rsatadi β buni yodlab oling, chunki yo'q narsaga murojaat qilsangiz xato olasiz:
| Hodisa | OLD bormi? |
NEW bormi? |
Izoh |
|---|---|---|---|
INSERT |
β yo'q | β bor | Eski qator yo'q β faqat yangisi keladi |
UPDATE |
β bor | β bor | Ikkalasi ham β eski va yangi holat |
DELETE |
β bor | β yo'q | Yangi qator yo'q β faqat o'chayotgani bor |
Yana bitta muhim qoida: NEWni faqat BEFORE triggerda o'zgartirish mumkin (SET NEW.ustun = ...). AFTER triggerda qator allaqachon yozib bo'lingan, shuning uchun u yerda NEWni o'zgartirsangiz hech narsa o'zgarmaydi (faqat o'qish mumkin). OLD esa hamisha faqat o'qish uchun β uni hech qachon o'zgartira olmaysiz.
π Qisqa xotira xaritasi: BEFORE + NEW = tahrirlash mumkin. Qolgan barcha kombinatsiyalarda (AFTERdagi NEW, har joydagi OLD) faqat o'qiysiz.
BEFORE: validatsiya va standart qiymat¶
BEFORE triggerning eng kuchli tomoni β qator diskka tushishidan oldin NEWni tuzatish yoki butun amalni rad etish. Ikkita asosiy vazifasini ko'ramiz.
1) Standart/hisoblangan qiymat berish¶
Kutubxonada: ijara qo'shilganda olingan_sana ko'rsatilmagan bo'lsa, bugungi sana avtomatik qo'yilsin.
USE kutubxona;
DELIMITER //
CREATE TRIGGER trg_ijara_bi_sana
BEFORE INSERT ON ijaralar
FOR EACH ROW
BEGIN
IF NEW.olingan_sana IS NULL THEN
SET NEW.olingan_sana = CURDATE();
END IF;
END //
DELIMITER ;
Endi INSERT INTO ijaralar (kitob_id, azo_id) VALUES (1, 5); desangiz, olingan_sana o'z-o'zidan bugungi sana bo'ladi.
2) Validatsiya: noto'g'ri ma'lumotni rad etish β SIGNAL¶
BEFORE trigger ichida SIGNAL bilan xato "otib", butun INSERT/UPDATEni bekor qilish mumkin. Dokon bazasida: mahsulot narxi manfiy bo'lib qolmasin.
USE dokon;
DELIMITER //
CREATE TRIGGER trg_mahsulot_bi_narx
BEFORE INSERT ON mahsulotlar
FOR EACH ROW
BEGIN
IF NEW.narx < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Narx manfiy bo''lishi mumkin emas';
END IF;
END //
DELIMITER ;
SQLSTATE '45000' β "foydalanuvchi aniqlagan istisno" uchun standart kod. SIGNAL ishlaganda trigger to'xtaydi va butun amal bekor bo'ladi (qator yozilmaydi). INSERT INTO mahsulotlar (nomi, narx) VALUES ('Test', -50); urinilsa, MySQL Narx manfiy bo'lishi mumkin emas xatosini qaytaradi.
π‘ Bir triggerda ham standartlash, ham validatsiya birga bo'lishi mumkin β masalan BEFORE INSERTda avval SET NEW.x = TRIM(NEW.x), keyin IF NEW.x = '' THEN SIGNAL .... Trigger tartibi muhim: avval tozalab, keyin tekshirasiz.
β οΈ Triggerdagi validatsiya CHECK cheklovining o'rnini bosadimi? Yo'q β ular bir-birini to'ldiradi. Oddiy "narx >= 0" kabi shartlar uchun CHECK constraint sodda va tezroq. Trigger esa boshqa jadvalga qarab tekshirish kerak bo'lganda (CHECK boshqa jadvalga murojaat qila olmaydi) yoki qiymatni o'zgartirish kerak bo'lganda kerak.
AFTER: audit, log va kaskad¶
AFTER triggerda fakt allaqachon ro'y bergan. Endi vazifa β uning izini boshqa jadvalga yozish yoki bog'liq ma'lumotni yangilash.
Kaskad: kitob ijaraga berilsa, nusxa sonini kamaytirish¶
Bobning boshidagi qoidani endi triggerga aylantiramiz:
USE kutubxona;
DELIMITER //
CREATE TRIGGER trg_ijara_ai_nusxa
AFTER INSERT ON ijaralar
FOR EACH ROW
BEGIN
UPDATE kitoblar
SET nusxa_soni = nusxa_soni - 1
WHERE id = NEW.kitob_id;
END //
DELIMITER ;
Endi har qanday INSERT INTO ijaralar β qaerdan kelmasin β kitoblar.nusxa_sonini avtomatik kamaytiradi. Mantiqiy juftligi: kitob qaytarilganda (qaytarilgan_sana to'lganda) nusxani qaytarish kerak β buni AFTER UPDATEda qilamiz:
USE kutubxona;
DELIMITER //
CREATE TRIGGER trg_ijara_au_qaytarish
AFTER UPDATE ON ijaralar
FOR EACH ROW
BEGIN
-- faqat endi qaytarilgan bo'lsa (eski NULL, yangi to'lgan)
IF OLD.qaytarilgan_sana IS NULL AND NEW.qaytarilgan_sana IS NOT NULL THEN
UPDATE kitoblar
SET nusxa_soni = nusxa_soni + 1
WHERE id = NEW.kitob_id;
END IF;
END //
DELIMITER ;
E'tibor bering β bu yerda OLD va NEWni solishtirib, faqat "haqiqatan qaytarildi" holatini ushladik. Aks holda har qanday UPDATE (masalan, faqat izoh o'zgartirilsa ham) nusxani noto'g'ri oshirib yuborardi.
π Bu "o'zgardimi yoki yo'qmi" tekshiruvi UPDATE triggerlarida juda muhim. UPDATE triggeri har UPDATEda ishlaydi, hatto qiymat aslida o'zgarmasa ham (UPDATE ... SET x = x). Shuning uchun IF OLD.ustun <> NEW.ustun (yoki NULL-xavfsiz IF NOT (OLD.ustun <=> NEW.ustun)) bilan haqiqiy o'zgarishni ajrating.
Audit log: kim, qachon, nimani o'chirdi¶
Klinika bazasida: bemor yozuvi o'chirilsa, izi bemorlar_arxiv jadvaliga tushsin (hech narsa izsiz yo'qolmasin).
USE klinika;
CREATE TABLE IF NOT EXISTS bemorlar_arxiv (
arxiv_id INT AUTO_INCREMENT PRIMARY KEY,
bemor_id INT,
ism VARCHAR(100),
tugilgan_sana DATE,
ochirgan VARCHAR(100),
ochirilgan_vaqt DATETIME
);
DELIMITER //
CREATE TRIGGER trg_bemor_ad_arxiv
AFTER DELETE ON bemorlar
FOR EACH ROW
BEGIN
INSERT INTO bemorlar_arxiv (bemor_id, ism, tugilgan_sana, ochirgan, ochirilgan_vaqt)
VALUES (OLD.id, OLD.ism, OLD.tugilgan_sana, CURRENT_USER(), NOW());
END //
DELIMITER ;
DELETE triggerda faqat OLD bor (yangi qator yo'q) β shuning uchun o'chayotgan qiymatlarni OLDdan olamiz. CURRENT_USER() kim o'chirayotganini, NOW() qachonligini yozadi. Endi DELETE FROM bemorlar WHERE id = 3; qilsangiz, yozuv yo'qolmaydi β arxivda qoladi.
Eskiβyangi tarix (audit/history) naqshi¶
Eng ko'p uchraydigan trigger vazifasi β har bir o'zgarishning tarixini saqlash. Masalan, dokondagi mahsulot narxi vaqt o'tib qanday o'zgargani kerak bo'ladi. Buning uchun alohida tarix jadvali tuzib, AFTER UPDATEda eski va yangi qiymatni yozamiz.
USE dokon;
CREATE TABLE IF NOT EXISTS narx_tarixi (
tarix_id INT AUTO_INCREMENT PRIMARY KEY,
mahsulot_id INT,
eski_narx DECIMAL(12,2),
yangi_narx DECIMAL(12,2),
ozgartirgan VARCHAR(100),
ozgargan_vaqt DATETIME
);
DELIMITER //
CREATE TRIGGER trg_mahsulot_au_narx_tarix
AFTER UPDATE ON mahsulotlar
FOR EACH ROW
BEGIN
IF NOT (OLD.narx <=> NEW.narx) THEN -- narx haqiqatan o'zgarsa
INSERT INTO narx_tarixi
(mahsulot_id, eski_narx, yangi_narx, ozgartirgan, ozgargan_vaqt)
VALUES
(NEW.id, OLD.narx, NEW.narx, CURRENT_USER(), NOW());
END IF;
END //
DELIMITER ;
<=> β NULL-xavfsiz tenglik operatori (ikkala tomon NULL bo'lsa ham to'g'ri ishlaydi). NOT (OLD.narx <=> NEW.narx) β "narx o'zgardi" degani. Endi har bir narx o'zgarishi narx_tarixida ketma-ket yozib boriladi:
UPDATE mahsulotlar SET narx = 12000 WHERE id = 1;
UPDATE mahsulotlar SET narx = 13500 WHERE id = 1;
SELECT * FROM narx_tarixi WHERE mahsulot_id = 1 ORDER BY ozgargan_vaqt;
-- ikki qator: 10000->12000, keyin 12000->13500
π‘ Bu naqsh "audit trail" (audit izi) deb ataladi va moliyaviy/tibbiy tizimlarda majburiy bo'ladi: "bu yozuv qachon, kim tomonidan, qanday o'zgargan?" degan savolga javob beradi. Tarix jadvalini hech qachon UPDATE/DELETE qilmang β u faqat o'sib boradigan jurnal bo'lsin.
Bir hodisaga bir nechta trigger: FOLLOWS va PRECEDES¶
MySQL 5.7 gacha bitta jadval+hodisa+vaqt kombinatsiyasiga faqat bitta trigger qo'yish mumkin edi. MySQL 8'da esa bir xil turdan bir nechta trigger bo'lishi mumkin β masalan, ikkita BEFORE INSERT. Bunday holda ularning ishlash tartibini boshqarish kerak: FOLLOWS (keyin) yoki PRECEDES (oldin).
USE dokon;
DELIMITER //
-- 1-trigger: nomni tozalash
CREATE TRIGGER trg_mahsulot_bi_tozalash
BEFORE INSERT ON mahsulotlar
FOR EACH ROW
BEGIN
SET NEW.nomi = TRIM(NEW.nomi);
END //
-- 2-trigger: tozalashdan KEYIN tekshirish
CREATE TRIGGER trg_mahsulot_bi_tekshir
BEFORE INSERT ON mahsulotlar
FOR EACH ROW
FOLLOWS trg_mahsulot_bi_tozalash -- avvalgisidan keyin ishlaydi
BEGIN
IF NEW.nomi = '' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Mahsulot nomi bo''sh bo''lishi mumkin emas';
END IF;
END //
DELIMITER ;
FOLLOWS trg_mahsulot_bi_tozalash β bu trigger nomi keltirilgan triggerdan keyin ishlasin. Demak avval nom tozalanadi (TRIM), keyin "bo'shmi?" deb tekshiriladi β to'g'ri tartib. Agar teskari kerak bo'lsa PRECEDES ishlatiladi.
π Tartibni ko'rish uchun SHOW TRIGGERSdagi ACTION_ORDER ustuniga qarang (INFORMATION_SCHEMA.TRIGGERS.ACTION_ORDER): 1, 2, 3... β bir guruh ichidagi navbat raqami.
β οΈ Bir hodisaga ko'p trigger qo'yish β kuchli, lekin xavfli. Ularning birgalikdagi natijasini kuzatish qiyinlashadi. Imkon bo'lsa, mantiqni bitta triggerda ketma-ket yozgan ma'qul; ko'p triggerni faqat mustaqil, bir-biriga bog'liq bo'lmagan vazifalar uchun ishlating.
Trigger cheklovlari: nimani QILA OLMAYDI¶
Trigger kuchli, lekin uning chegaralari bor. Bularni bilmasangiz, kutilmagan xatolarga duch kelasiz.
1) O'z jadvalini DML qila olmaydi (1442-xato)¶
Trigger o'zi biriktirilgan jadvalni INSERT/UPDATE/DELETE qila olmaydi. Aks holda cheksiz rekursiya bo'lardi (trigger o'zini qayta chaqiradi). Quyidagi kod xato beradi:
USE dokon;
DELIMITER //
CREATE TRIGGER trg_xato
AFTER UPDATE ON mahsulotlar
FOR EACH ROW
BEGIN
-- XATO 1442: o'z jadvalini UPDATE qilolmaydi
UPDATE mahsulotlar SET narx = narx * 1.1 WHERE id = NEW.id;
END //
DELIMITER ;
ERROR 1442: Can't update table 'mahsulotlar' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
Yechim: o'z qatorini o'zgartirish kerak bo'lsa, UPDATE emas, BEFORE triggerda SET NEW.ustun = ... ishlating. Yuqoridagi misolni to'g'ri qilib:
USE dokon;
DELIMITER //
CREATE TRIGGER trg_togri
BEFORE UPDATE ON mahsulotlar
FOR EACH ROW
BEGIN
SET NEW.narx = NEW.narx * 1.1; -- to'g'ri: NEW orqali, qator yozilishidan oldin
END //
DELIMITER ;
π Demak "qatorning o'zini o'zgartirish" = BEFORE + SET NEW. "Boshqa jadvalni o'zgartirish" = AFTER + UPDATE/INSERT. Bu ikkisini aralashtirmang.
2) COMMIT/ROLLBACK ishlatib bo'lmaydi¶
Trigger har doim uni chaqirgan amal tranzaksiyasi ichida ishlaydi. Shuning uchun trigger ichida COMMIT, ROLLBACK yoki START TRANSACTION yozsangiz β xato olasiz. Trigger tranzaksiyani o'zi yakunlay olmaydi: agar asosiy amal bekor qilinsa (ROLLBACK), trigger qilgan o'zgarishlar ham birga bekor bo'ladi (atomarlik). Trigger ichida amalni "to'xtatish" uchun yagona yo'l β SIGNAL bilan xato otish; u butun amalni (trigger o'zgarishlari bilan birga) bekor qiladi.
3) Ko'rinmaslik va kaskad zanjir xavfi¶
Trigger boshqa jadvalni o'zgartirsa, o'sha jadvalning triggeri ham ishga tushadi β shunday qilib trigger zanjiri hosil bo'ladi. MySQL'da maksimal trigger ichma-ichligi cheklangan, lekin baribir bu zanjirni boshqarish qiyin. AβBβC ketma-ketlikni kod o'qib bilib bo'lmaydi (hech qaysi INSERTda yozilmagan).
β οΈ MySQL'da bir trigger ichidan CALL orqali stored procedureni chaqirish mumkin, lekin u procedure'da COMMIT/ROLLBACK bo'lmasligi shart. Triggerlar LOCK TABLES, ALTER TABLE, DROP TABLE kabi tranzaksiyani buzadigan buyruqlarni ham bajara olmaydi.
"Ko'rinmas sehr" ogohlantirishi: qachon foydali, qachon zararli¶
Trigger β bu yashirin kod. INSERT INTO ijaralar ... yozgan dasturchi kitoblar.nusxa_soni o'zgarganini ko'rmaydi β bu "sehr" parda ortida sodir bo'ladi. Ana shu yashirinlik triggerning ham eng katta foydasi, ham eng katta xavfi.
Trigger foydali bo'lgan holatlar:
- Audit/tarix β har o'zgarishni jurnalga yozish. Bu mantiqni ilovaga tarqatish xato qilishga olib keladi; triggerda bitta joyda, ishonchli.
- Qat'iy invariant β "ijara qo'shilsa, nusxa kamaysin" kabi qoida hech qachon buzilmasligi shart va u qaerdan kelishidan qat'i nazar amal qilishi kerak.
- Hosila qiymat β
BEFOREda hisoblangan ustun (masalan, to'liq ism = ism + familiya) ni avtomatik to'ldirish.
Trigger zararli bo'lgan holatlar:
- Murakkab biznes-mantiq β ko'p shartli, ko'p jadvalli logikani triggerga tiqish. Buni ilovada yoki stored procedure'da ko'rish va sinash osonroq.
- Nojo'ya effektlar β bitta
UPDATEko'zga ko'rinmas ravishda 5 ta jadvalni o'zgartirsa, hatto tajribali dasturchi ham nima sodir bo'layotganini tushunmay qoladi (debug qilish azob). - Tezlik β har qatorga og'ir trigger ulkan
INSERTni sekinlashtiradi (1 mln qator = 1 mln trigger ishlashi).
π Oltin qoida: trigger β qoida (invariant) va audit uchun, biznes-jarayon uchun emas. "Bu narsa hamisha, istisnosiz to'g'ri bo'lishi kerakmi?" β ha bo'lsa, trigger. "Bu β bir necha bosqichli ish jarayonimi?" β unda procedure yoki ilova kodi.
π‘ Triggerlaringizni hujjatlashtiring: jadval nomidan keyin _bi_/_ai_/_bu_/_au_/_bd_/_ad_ kabi prefiks (before/after Γ insert/update/delete) qo'ying. Shunda SHOW TRIGGERS natijasidan har birining nima qilishi nomidan ko'rinib turadi.
Triggerlarni ko'rish va o'chirish¶
Mavjud triggerlarni ko'rishning bir necha yo'li bor:
USE kutubxona;
-- Joriy bazadagi barcha triggerlar
SHOW TRIGGERS;
-- Faqat ma'lum jadvalga tegishlilari
SHOW TRIGGERS WHERE `Table` = 'ijaralar';
-- Bitta triggerning to'liq ta'rifi
SHOW CREATE TRIGGER trg_ijara_ai_nusxa;
INFORMATION_SCHEMA orqali kengroq so'rov:
SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE,
ACTION_TIMING, ACTION_ORDER
FROM INFORMATION_SCHEMA.TRIGGERS
WHERE TRIGGER_SCHEMA = 'kutubxona'
ORDER BY EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_ORDER;
| Ustun | Ma'nosi |
|---|---|
EVENT_MANIPULATION |
INSERT / UPDATE / DELETE |
ACTION_TIMING |
BEFORE / AFTER |
EVENT_OBJECT_TABLE |
Trigger biriktirilgan jadval |
ACTION_ORDER |
Bir guruhdagi ishlash tartibi (1, 2, ...) |
Triggerni o'chirish:
DROP TRIGGER trg_ijara_ai_nusxa;
DROP TRIGGER IF EXISTS trg_ijara_ai_nusxa; -- yo'q bo'lsa ham xato bermaydi
β οΈ MySQL'da triggerni ALTER qilib bo'lmaydi β o'zgartirmoqchi bo'lsangiz DROP qilib, qaytadan CREATE qilasiz. Jadval DROP TABLE qilinsa, unga osilgan barcha triggerlar ham avtomatik o'chadi.
EVENT: bazadagi budilnik¶
Trigger β hodisaga javob beradi (kimdir INSERT qilsa). Event esa vaqtga javob beradi: belgilangan paytda yoki har N daqiqada o'zi uyg'onib SQL bajaradi. Bu β bazaning ichidagi vazifa rejalashtiruvchisi (operatsion tizimning cron/Task Scheduleriga o'xshash, lekin baza ichida).
event_scheduler: ON/OFF¶
Eventlar faqat event scheduler (rejalashtiruvchi jarayon) yoqilgan bo'lsa ishlaydi. U sukut bo'yicha o'chiq bo'lishi mumkin β avval holatini tekshiring:
Yoqish (joriy sessiya emas, butun server uchun):
β οΈ SET GLOBAL faqat server qayta ishga tushgunicha amal qiladi. Doimiy yoqish uchun my.cnf (yoki my.ini) faylida [mysqld] bo'limiga event_scheduler=ON yozing. Aks holda server restart bo'lganda eventlar yana to'xtaydi. Bu eng ko'p uchraydigan tuzoq: "eventim ishlamayapti" β odatda scheduler o'chiq bo'ladi.
CREATE EVENT: takroriy va bir martalik¶
Event ikki xil rejada bo'ladi: ma'lum vaqtda bir marta (AT) yoki har N vaqtda takroran (EVERY).
Har N vaqtda (takroriy): kutubxonada har kuni 3 oydan oshgan, hali qaytarilmagan ijaralar bo'yicha tekshiruv jadvaliga yozuv tashlash.
USE kutubxona;
CREATE TABLE IF NOT EXISTS qarzdor_hisobot (
id INT AUTO_INCREMENT PRIMARY KEY,
sana DATE,
qarzdorlar_soni INT
);
DELIMITER //
CREATE EVENT ev_kunlik_qarzdor
ON SCHEDULE EVERY 1 DAY
STARTS '2026-06-12 02:00:00'
DO
BEGIN
INSERT INTO qarzdor_hisobot (sana, qarzdorlar_soni)
SELECT CURDATE(), COUNT(*)
FROM ijaralar
WHERE qaytarilgan_sana IS NULL
AND olingan_sana < CURDATE() - INTERVAL 90 DAY;
END //
DELIMITER ;
ON SCHEDULE EVERY 1 DAY β har kuni; STARTS β birinchi marta qachondan boshlash. EVERY qiymatlari: EVERY 10 MINUTE, EVERY 1 HOUR, EVERY 1 WEEK va h.k.
Bir martalik (AT): ma'lum sanada bir marta arxivlash.
USE dokon;
CREATE EVENT ev_bir_martalik_arxiv
ON SCHEDULE AT '2026-12-31 23:00:00'
DO
INSERT INTO buyurtmalar_arxiv
SELECT * FROM buyurtmalar WHERE YEAR(sana) = 2026;
AT '2026-12-31 23:00:00' β aynan shu paytda bir marta ishlaydi. Tana bitta buyruqdan iborat bo'lsa BEGIN ... END shart emas (yuqoridagidek) β shuning uchun bu yerda DELIMITER ham kerak emas.
ON COMPLETION PRESERVE: eventni saqlab qolish¶
Sukut bo'yicha bir martalik (AT) event ishlab bo'lgach, MySQL uni avtomatik o'chirib tashlaydi (ON COMPLETION NOT PRESERVE). Agar eventni ishlab bo'lgach ham bazada (o'chgan holatda) qoldirmoqchi bo'lsangiz β ON COMPLETION PRESERVE qo'shing:
USE dokon;
CREATE EVENT ev_yil_yakuni_hisobot
ON SCHEDULE AT '2026-12-31 23:30:00'
ON COMPLETION PRESERVE -- ishlab bo'lgach ham o'chmaydi
DO
INSERT INTO yillik_hisobot (yil, jami_savdo)
SELECT 2026, SUM(summa) FROM buyurtmalar WHERE YEAR(sana) = 2026;
π PRESERVE β eventning ta'rifi (tarixi) kerak bo'lganda yoki keyinroq ALTER EVENTbilan qayta yoqmoqchi bo'lganda foydali. Takroriy (EVERY) eventlar esa o'z-o'zidan saqlanadi β ular hech qachon "tugamaydi" (agar ENDS ko'rsatilmagan bo'lsa).
ENABLE / DISABLE va ALTER EVENT¶
Eventni o'chirmasdan vaqtincha to'xtatib turish mumkin:
USE kutubxona;
ALTER EVENT ev_kunlik_qarzdor DISABLE; -- vaqtincha o'chirish
ALTER EVENT ev_kunlik_qarzdor ENABLE; -- qayta yoqish
ALTER EVENT bilan jadvalni o'zgartirmasdan jadvani (jadvalni emas β jadval-rejani, ya'ni schedule'ni), tanani yoki nomini ham o'zgartirish mumkin:
-- Jadvalni (vaqt rejasini) o'zgartirish
ALTER EVENT ev_kunlik_qarzdor
ON SCHEDULE EVERY 12 HOUR;
-- Tanani (DO qismini) o'zgartirish
DELIMITER //
ALTER EVENT ev_kunlik_qarzdor
DO
BEGIN
-- yangilangan mantiq
DELETE FROM qarzdor_hisobot WHERE sana < CURDATE() - INTERVAL 1 YEAR;
END //
DELIMITER ;
-- Nomini o'zgartirish
ALTER EVENT ev_kunlik_qarzdor RENAME TO ev_qarzdor_kunlik;
π‘ Triggerni ALTER qilib bo'lmaydi, lekin eventni β bo'ladi. Bu ikkisining muhim farqi. Eventni sinash uchun esa odatda uni qo'lda bir marta ishlatib ko'rish kerak β buning uchun event tanasini alohida procedure'ga ajratib, o'sha procedure'ni CALL qilib tekshirish qulay (event ichidan procedure'ni CALL qilish mumkin).
Eventning amaliy holatlari¶
Event qaerda real foyda beradi? Uchta klassik naqsh.
1) Eski log/ma'lumotni tozalash¶
Audit jadvallari cheksiz o'sadi. Eski yozuvlarni davriy o'chirib turish kerak. Klinika bazasida: 1 yildan eski audit yozuvlarini har kecha o'chirish.
USE klinika;
CREATE EVENT ev_eski_arxiv_tozalash
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP + INTERVAL 1 DAY
DO
DELETE FROM bemorlar_arxiv
WHERE ochirilgan_vaqt < NOW() - INTERVAL 1 YEAR;
2) Kunlik rollup/hisobot jadvali (materialized view simulyatsiyasi)¶
Og'ir agregat har safar so'ralganda hisoblanmasligi uchun, natijani kechasi bir marta hisoblab, tayyor jadvalga yozib qo'yamiz. Taksi bazasida: har haydovchining kunlik daromadi.
USE taksi;
CREATE TABLE IF NOT EXISTS haydovchi_kunlik (
sana DATE,
haydovchi_id INT,
safarlar INT,
daromad DECIMAL(12,2),
PRIMARY KEY (sana, haydovchi_id)
);
DELIMITER //
CREATE EVENT ev_kunlik_rollup
ON SCHEDULE EVERY 1 DAY
STARTS '2026-06-12 01:00:00'
DO
BEGIN
INSERT INTO haydovchi_kunlik (sana, haydovchi_id, safarlar, daromad)
SELECT CURDATE() - INTERVAL 1 DAY,
haydovchi_id,
COUNT(*),
SUM(narx)
FROM safarlar
WHERE DATE(boshlanish) = CURDATE() - INTERVAL 1 DAY
GROUP BY haydovchi_id
ON DUPLICATE KEY UPDATE
safarlar = VALUES(safarlar),
daromad = VALUES(daromad);
END //
DELIMITER ;
Endi kunlik daromad hisoboti haydovchi_kunlik jadvalidan bir lahzada o'qiladi β har safar millionlab safarlar qatorini qayta agregat qilmaydi. ON DUPLICATE KEY UPDATE β event ikki marta ishlasa ham takror yozuv qo'shmaydi (idempotent).
3) Arxivlash: eski ma'lumotni boshqa jadvalga ko'chirish¶
Dokonda: o'tgan yildan eski buyurtmalarni arxiv jadvaliga ko'chirib, asosiy jadvalni yengillashtirish.
USE dokon;
DELIMITER //
CREATE EVENT ev_oylik_arxiv
ON SCHEDULE EVERY 1 MONTH
DO
BEGIN
INSERT INTO buyurtmalar_arxiv
SELECT * FROM buyurtmalar
WHERE sana < CURDATE() - INTERVAL 1 YEAR;
DELETE FROM buyurtmalar
WHERE sana < CURDATE() - INTERVAL 1 YEAR;
END //
DELIMITER ;
β οΈ Bu yerda avval INSERT, keyin DELETE β agar INSERT xato bersa, DELETE ham bajarilmasligi uchun ularni tranzaksiya ichida bajargan ma'qul. Event tanasida START TRANSACTION ... COMMIT ishlatish mumkin (triggerdan farqli β event mustaqil tranzaksiyada ishlaydi). Real loyihada ko'chirib bo'lganini tasdiqlamasdan o'chirmang.
Eventlarni ko'rish va o'chirish¶
USE taksi;
-- Joriy bazadagi eventlar
SHOW EVENTS;
-- Bitta eventning to'liq ta'rifi
SHOW CREATE EVENT ev_kunlik_rollup;
-- INFORMATION_SCHEMA orqali kengroq
SELECT EVENT_NAME, STATUS, EVENT_TYPE, INTERVAL_VALUE, INTERVAL_FIELD,
LAST_EXECUTED, STARTS, ENDS, ON_COMPLETION
FROM INFORMATION_SCHEMA.EVENTS
WHERE EVENT_SCHEMA = 'taksi';
| Ustun | Ma'nosi |
|---|---|
STATUS |
ENABLED / DISABLED / SLAVESIDE_DISABLED |
EVENT_TYPE |
ONE TIME (AT) yoki RECURRING (EVERY) |
LAST_EXECUTED |
Oxirgi marta qachon ishlagani (sinash uchun muhim) |
ON_COMPLETION |
PRESERVE yoki NOT PRESERVE |
Eventni o'chirish:
π LAST_EXECUTED NULL bo'lsa β event hali bironta ham ishlamagan. Bu odatda event_scheduler = OFF ekanini yoki STARTS vaqti hali kelmaganini bildiradi. Eventni debug qilishni shu ustundan boshlang.
Trigger va Event: qisqa qiyos¶
| Xususiyat | Trigger | Event |
|---|---|---|
| Nima qo'zg'atadi | Hodisa (INSERT/UPDATE/DELETE) |
Vaqt (AT / EVERY) |
| Bog'langan | Bitta jadvalga | Butun bazaga (jadvalga emas) |
OLD/NEW bormi |
Ha | Yo'q |
COMMIT/ROLLBACK |
Mumkin emas | Mumkin (mustaqil tranzaksiya) |
ALTER qilinadimi |
Yo'q (drop+create) | Ha (ALTER EVENT) |
| Yoqish sharti | Doim faol | event_scheduler = ON kerak |
Xulosa¶
Trigger β jadvalga biriktirilgan, hodisaga avtomatik javob beradigan soqchi. 6 turi bor (BEFORE/AFTER Γ INSERT/UPDATE/DELETE). BEFORE triggerda SET NEW.ustunbilan qiymatni tuzatish yoki SIGNAL bilan amalni rad etish mumkin; AFTERda esa fakt ro'y bergach audit/tarix/kaskad uchun ishlatasiz. OLD va NEW psevdo-jadvallari qatorning eski va yangi holatiga murojaat beradi (INSERTda faqat NEW, DELETEda faqat OLD, UPDATEda ikkalasi). Trigger o'z jadvalini INSERT/UPDATE/DELETE qila olmaydi (1442-xato β buning o'rniga BEFORE+SET NEW) va COMMIT/ROLLBACK ishlata olmaydi. Eng muhim qoida: trigger β qoida (invariant) va audit uchun, murakkab biznes-mantiq uchun emas, chunki uning yashirinligi katta xavf. Event β bazadagi budilnik: event_scheduler = ON bo'lsa, AT (bir marta) yoki EVERY (takroriy) rejada SQL bajaradi; ON COMPLETION PRESERVE bir martalik eventni saqlab qoladi, ALTER EVENT bilan uni o'zgartirish, ENABLE/DISABLE bilan to'xtatib turish mumkin. Eventning klassik vazifalari β eski log tozalash, kunlik rollup/hisobot va arxivlash. Keyingi bobda stored program ichidagi eng nozik vositaga β natijani qatorma-qator aylanadigan cursorga o'tamiz.
24-bob masalalari¶
- (kutubxona)
ijaralarjadvaligaBEFORE INSERTtrigger yozing:olingan_sanaNULLbo'lsa, avtomatikCURDATE()qo'ysin - (dokon)
mahsulotlarjadvaligaBEFORE INSERTtrigger:narx < 0bo'lsaSIGNAL SQLSTATE '45000'bilan rad etsin - (kutubxona)
AFTER INSERT ON ijaralartrigger yozing: tegishli kitobningnusxa_sonini bittaga kamaytirsin - (kutubxona)
AFTER UPDATE ON ijaralartrigger:qaytarilgan_sanaNULLdan to'lganga o'zgarsagina, kitobnusxa_sonini bittaga oshirsin (OLD/NEWsolishtiring) - (klinika)
bemorlar_arxivjadvalini yarating vaAFTER DELETE ON bemorlartrigger yozing: o'chirilgan bemor ma'lumotini,CURRENT_USER()vaNOW()bilan birga arxivga yozsin - (dokon)
narx_tarixijadvalini yarating vaAFTER UPDATE ON mahsulotlartrigger yozing: narx haqiqatan o'zgarganda (<=>ishlatib) eski va yangi narxni tarixga yozsin - (taksi)
BEFORE INSERT ON safarlartrigger:narxkiritilmagan (NULL) bo'lsa, masofaga qarab hisoblab qo'ysin (SET NEW.narx = NEW.masofa * 2500kabi) - (dokon)
mahsulotlarga ikkitaBEFORE INSERTtrigger yozing: birinchinominiTRIMqilsin, ikkinchisi (FOLLOWSbilan birinchidan keyin) bo'sh nomniSIGNALbilan rad etsin - (kutubxona)
SHOW TRIGGERS WHERE \Table` = 'ijaralar';bajaring βijaralarga osilgan barcha triggerlar ro'yxatini va ularningTiming/Event` ustunlarini ko'ring - (dokon)
INFORMATION_SCHEMA.TRIGGERSdandokonbazasidagi triggerlarniEVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_ORDERbo'yicha tartiblab chiqaring - (kutubxona) Atayin xato qiling:
AFTER UPDATE ON kitoblartriggerda o'shakitoblarniUPDATEqilishga urinib ko'ring β qaysi xato (1442) chiqadi? Keyin uniBEFORE+SET NEWbilan to'g'rilang - (klinika)
qabullarjadvaligaBEFORE INSERTtrigger:qabul_narxi0 yoki manfiy bo'lsa rad etsin,NULLbo'lsa standart50000qo'ysin (bir triggerda ikki shart) - (taksi)
haydovchilargaAFTER UPDATEtrigger: agarreyting4.0 dan pastga tushsa,ogohlantirish_logjadvaliga yozuv tashlasin - (umumiy)
SHOW VARIABLES LIKE 'event_scheduler';bilan holatni tekshiring;OFFbo'lsaSET GLOBAL event_scheduler = ON;bilan yoqing va qayta tekshiring - (kutubxona)
EVERY 1 DAYtakroriy event yozing: har kuni 90 kundan oshgan, qaytarilmagan ijaralar soniniqarzdor_hisobotjadvaliga yozsin (STARTSbilan boshlanish vaqtini bering) - (dokon)
ON SCHEDULE ATbilan bir martalik event yozing vaON COMPLETION PRESERVEqo'shing;SHOW EVENTSda uningSTATUSva turini tekshiring - (taksi)
haydovchi_kunlikrollup jadvalini yarating va har kuni kechasi avvalgi kun daromadiniON DUPLICATE KEY UPDATEbilan yozadigan event yozing - (klinika) 1 yildan eski
bemorlar_arxivyozuvlarini har kuni o'chiradigan tozalash eventi yozing; keyinALTER EVENT ... DISABLEbilan vaqtincha to'xtating vaSHOW EVENTSdaSTATUSni ko'ring - (dokon)
ALTER EVENTbilan 15-yoki-17-masaladagi eventning jadvalini (ON SCHEDULE)EVERY 12 HOURga o'zgartiring, so'ng nominiRENAME TObilan o'zgartiring;INFORMATION_SCHEMA.EVENTSdanLAST_EXECUTEDni tekshiring - Fikrlang va yozing: "har bir buyurtma qo'shilganda mijozning umumiy xarid summasini yangilash" qoidasini β
AFTER INSERTtrigger bilan qilgan ma'qulmi yoki har kuniEVENTbilan qayta hisoblagan ma'qulmi? Har tarafga 2 tadan argument toping (maslahat: ma'lumot qanchalik tez yangilanishi kerak, va trigger zanjiri/tezlik xavfi qanchalik muhim β shu ikki savolga tayaning)