13-bo‘lim

Tashqi kalitlar va referensial butunlik

Yetim qatorlar, ON DELETE va ON UPDATE qoidalari, CASCADE xavfi, yumshoq o'chirish va tashqi kalitsiz ishlash.

🕑 21 daqiqa o‘qish 📄 941 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Yetim qator muammosi
  2. Tashqi kalit bilan himoya
  3. ON DELETE qoidalari
  4. CASCADE amalda
  5. SET NULL
  6. ON UPDATE
  7. Yumshoq o'chirish
  8. Tashqi kalitsiz ishlash
  9. Yetimlarni topish
  10. Xulosa

Tashqi kalit - jadvallar orasidagi bog'lanishni majburiy qiladi. Usiz sxema tez orada "yetim" qatorlar bilan to'lib ketadi.

Yetim qator muammosi #

SQL
CREATE TABLE mualliflar (
    id  INT PRIMARY KEY AUTO_INCREMENT,
    ism VARCHAR(100) NOT NULL
);

-- Tashqi kalitsiz jadval
CREATE TABLE kitoblar_himoyasiz (
    id         INT PRIMARY KEY AUTO_INCREMENT,
    sarlavha   VARCHAR(200) NOT NULL,
    muallif_id INT NOT NULL
);

INSERT INTO mualliflar (ism) VALUES ('Abdulla Qodiriy'), ('Cho''lpon');

INSERT INTO kitoblar_himoyasiz (sarlavha, muallif_id) VALUES
    ('O''tkan kunlar', 1),
    ('Kecha va kunduz', 2),
    ('Sirli kitob', 999);          -- 999-muallif MAVJUD EMAS
SQL
SELECT k.sarlavha, k.muallif_id, COALESCE(m.ism, '(YETIM)') AS muallif
FROM kitoblar_himoyasiz k
LEFT JOIN mualliflar m ON m.id = k.muallif_id
ORDER BY k.id;
Natija
+-----------------+------------+-----------------+
| sarlavha        | muallif_id | muallif         |
+-----------------+------------+-----------------+
| O'tkan kunlar   |          1 | Abdulla Qodiriy |
| Kecha va kunduz |          2 | Cho'lpon        |
| Sirli kitob     |        999 | (YETIM)         |
+-----------------+------------+-----------------+
Yetim qatorlar jimgina to'planadi

999 raqamli muallif yo'q, lekin ma'lumotlar bazasi buni qabul qildi.

Oqibatlari:

MuammoKo'rinishi
INNER JOIN qatorni yo'qotadiKitob ro'yxatda ko'rinmaydi
LEFT JOIN NULL beradi"muallif: bo'sh"
Hisobotlar mos kelmaydiKitoblar soni turlicha chiqadi
Xato manbasini topib bo'lmaydiQachon va kim yozgani noma'lum

Va eng yomoni - buni hech kim sezmaydi, toki mijoz "kitobim yo'qolib qoldi" demaguncha.

Tashqi kalit bunday qatorni jismonan yozdirmaydi:

SQL
INSERT INTO kitoblar (sarlavha, muallif_id) VALUES ('Sirli', 999);
-- ERROR 1452: Cannot add or update a child row

Xato darhol, yozish paytida chiqadi - oylar keyin emas.

Tashqi kalit bilan himoya #

SQL
CREATE TABLE kitoblar (
    id         INT PRIMARY KEY AUTO_INCREMENT,
    sarlavha   VARCHAR(200) NOT NULL,
    muallif_id INT NOT NULL,
    CONSTRAINT fk_kitob_muallif
        FOREIGN KEY (muallif_id) REFERENCES mualliflar(id)
);

INSERT INTO kitoblar (sarlavha, muallif_id) VALUES
    ('O''tkan kunlar', 1),
    ('Mehrobdan chayon', 1),
    ('Kecha va kunduz', 2);
SQL
SELECT m.ism, k.sarlavha
FROM kitoblar k
JOIN mualliflar m ON m.id = k.muallif_id
ORDER BY k.id;
Natija
+-----------------+------------------+
| ism             | sarlavha         |
+-----------------+------------------+
| Abdulla Qodiriy | O'tkan kunlar    |
| Abdulla Qodiriy | Mehrobdan chayon |
| Cho'lpon        | Kecha va kunduz  |
+-----------------+------------------+
SQL
-- Mavjud bo'lmagan muallif rad etiladi
INSERT INTO kitoblar (sarlavha, muallif_id) VALUES ('Sirli kitob', 999);
Natija
ERROR 1452 (23000): Cannot add or update a child row: a foreign key
constraint fails (`maktab`.`kitoblar`, CONSTRAINT `fk_kitob_muallif`
FOREIGN KEY (`muallif_id`) REFERENCES `mualliflar` (`id`))
SQL
-- Kitobi bor muallifni o'chirib bo'lmaydi
DELETE FROM mualliflar WHERE id = 1;
Natija
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key
constraint fails (`maktab`.`kitoblar`, CONSTRAINT `fk_kitob_muallif`
FOREIGN KEY (`muallif_id`) REFERENCES `mualliflar` (`id`))

ON DELETE qoidalari #

Ota qator o'chirilganda bola bilan nima bo'ladi? RESTRICT / NO ACTION O'chirishga YO'L QO'YMAYDI - standart va eng xavfsiz Bolasi bor otani o'chirish uchun avval bolalarni hal qiling CASCADE Bola qatorlarni HAM O'CHIRADI - zanjirli Kuchli, lekin ehtiyotkorlik talab qiladi SET NULL Tashqi kalitni NULL ga aylantiradi Ustun NULL ga ruxsat berishi kerak SET DEFAULT Standart qiymatga qaytaradi InnoDB QO'LLAB-QUVVATLAMAYDI
Standart qiymat - RESTRICT, ya'ni himoya yoqilgan

CASCADE amalda #

SQL
CREATE TABLE buyurtmalar (
    id     INT PRIMARY KEY AUTO_INCREMENT,
    mijoz  VARCHAR(100) NOT NULL
);

CREATE TABLE buyurtma_qatorlari (
    id           INT PRIMARY KEY AUTO_INCREMENT,
    buyurtma_id  INT NOT NULL,
    mahsulot     VARCHAR(100) NOT NULL,
    CONSTRAINT fk_qator_buyurtma
        FOREIGN KEY (buyurtma_id) REFERENCES buyurtmalar(id)
        ON DELETE CASCADE
);

INSERT INTO buyurtmalar (mijoz) VALUES ('Husanboy'), ('Malika');

INSERT INTO buyurtma_qatorlari (buyurtma_id, mahsulot) VALUES
    (1, 'Noutbuk'), (1, 'Sichqoncha'), (2, 'Klaviatura');
SQL
SELECT
    (SELECT COUNT(*) FROM buyurtmalar)        AS buyurtmalar,
    (SELECT COUNT(*) FROM buyurtma_qatorlari) AS qatorlar;
Natija
+-------------+----------+
| buyurtmalar | qatorlar |
+-------------+----------+
|           2 |        3 |
+-------------+----------+
SQL
-- Buyurtmani o'chirsak, uning qatorlari ham o'chadi
DELETE FROM buyurtmalar WHERE id = 1;

SELECT
    (SELECT COUNT(*) FROM buyurtmalar)        AS buyurtmalar,
    (SELECT COUNT(*) FROM buyurtma_qatorlari) AS qatorlar;
Natija
+-------------+----------+
| buyurtmalar | qatorlar |
+-------------+----------+
|           1 |        1 |
+-------------+----------+
CASCADE - kuchli va xavfli

CASCADE zanjirli ishlaydi. Uch darajali sxemani tasavvur qiling:

Natija
foydalanuvchilar -> buyurtmalar -> qatorlar -> to'lovlar

Har bog'lanishda ON DELETE CASCADE bo'lsa, bitta buyruq:

SQL
DELETE FROM foydalanuvchilar WHERE id = 5;

to'rt jadvaldan ma'lumot o'chiradi. Va bu ogohlantirishsiz sodir bo'ladi.

Real hodisa naqshi: administrator "test foydalanuvchini" o'chiradi, keyin ma'lum bo'ladiki, u yerda ming yozuv bor edi.

CASCADE qachon to'g'ri:

HolatNima uchun
Buyurtma → qatorlariQator buyurtmasiz ma'nosiz
Maqola → izohlariIzoh maqolasiz ma'nosiz
Foydalanuvchi → sessiyalariSessiya yo'qolishi normal
Bog'lovchi jadval yozuvlariBog'lanish o'zi ma'no bermaydi

CASCADE qachon xavfli:

HolatNima uchun
Mijoz → buyurtmalariBuyurtma moliyaviy hujjat
Xodim → hisobotlariTarix yo'qoladi
Mahsulot → sotuvlariStatistika buziladi

Qoida: bola qator mustaqil qiymatga ega bo'lsa - CASCADE ishlatmang. RESTRICT qoldiring va o'chirishni ongli qiling.

SET NULL #

SQL
CREATE TABLE bolimlar (
    id  INT PRIMARY KEY AUTO_INCREMENT,
    nom VARCHAR(50) NOT NULL
);

CREATE TABLE xodimlar (
    id       INT PRIMARY KEY AUTO_INCREMENT,
    ism      VARCHAR(100) NOT NULL,
    bolim_id INT NULL,
    CONSTRAINT fk_xodim_bolim
        FOREIGN KEY (bolim_id) REFERENCES bolimlar(id)
        ON DELETE SET NULL
);

INSERT INTO bolimlar (nom) VALUES ('IT'), ('Hisobot');

INSERT INTO xodimlar (ism, bolim_id) VALUES
    ('Husanboy', 1), ('Malika', 1), ('Kamola', 2);
SQL
-- Bo'limni o'chirsak, xodimlar QOLADI, lekin bo'limsiz
DELETE FROM bolimlar WHERE id = 1;

SELECT ism, COALESCE(CAST(bolim_id AS CHAR), '(bolimsiz)') AS bolim
FROM xodimlar
ORDER BY id;
Natija
+----------+------------+
| ism      | bolim      |
+----------+------------+
| Husanboy | (bolimsiz) |
| Malika   | (bolimsiz) |
| Kamola   | 2          |
+----------+------------+
SET NULL - ustun NULL ga ruxsat berishi shart
SQL
bolim_id INT NULL,                    -- NOT NULL bo'lsa ishlamaydi
FOREIGN KEY (bolim_id) ... ON DELETE SET NULL

bolim_id NOT NULL bo'lsa, SET NULL qoidasini yaratishga urinish xato beradi.

Bu mantiqiy: qiymatni NULL ga o'rnatib bo'lmasa, qoida bajarilmaydi.

SET NULL o'rinli holatlar:

  • Xodim → bo'lim (bo'lim yopilsa, xodim qoladi);
  • Maqola → rukn (rukn o'chirilsa, maqola "ruknsiz" bo'ladi);
  • Vazifa → mas'ul shaxs (xodim ketsa, vazifa qoladi).

Ya'ni: bog'lanish ixtiyoriy bo'lgan joyda.

ON UPDATE #

Bu bo'limda yaratilgan uchala tashqi kalit qoidasini bir joyda ko'ramiz:

SQL
SELECT
    CONSTRAINT_NAME AS cheklov,
    UPDATE_RULE     AS yangilashda,
    DELETE_RULE     AS ochirishda
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE()
ORDER BY BINARY CONSTRAINT_NAME;
Natija
+-------------------+-------------+------------+
| cheklov           | yangilashda | ochirishda |
+-------------------+-------------+------------+
| fk_kitob_muallif  | RESTRICT    | RESTRICT   |
| fk_qator_buyurtma | RESTRICT    | CASCADE    |
| fk_xodim_bolim    | RESTRICT    | SET NULL   |
+-------------------+-------------+------------+
ON UPDATE CASCADE kamdan-kam kerak

ON UPDATE CASCADE ota qatorning kaliti o'zgarganda bola qatorlarni yangilaydi.

Lekin 3-bo'limda ko'rganimizdek, sun'iy kalit hech qachon o'zgarmaydi. Shuning uchun sun'iy kalit ishlatsangiz, ON UPDATE deyarli hech qachon ishga tushmaydi.

U faqat tabiiy kalit ishlatilganda kerak bo'ladi:

SQL
-- Valyuta kodi kalit bo'lsa
valyuta CHAR(3) PRIMARY KEY       -- 'UZS', 'USD'

Kod o'zgarsa (kamdan-kam, lekin bo'ladi), bog'liq jadvallar avtomatik yangilanadi.

Standart qiymat - RESTRICT, ya'ni kalitni o'zgartirishga yo'l qo'yilmaydi. Ko'p holatda bu to'g'ri.

Yumshoq o'chirish #

SQL
CREATE TABLE mijozlar (
    id         INT PRIMARY KEY AUTO_INCREMENT,
    ism        VARCHAR(100) NOT NULL,
    ochirilgan DATETIME NULL DEFAULT NULL
);

CREATE TABLE buyurtmalar_yumshoq (
    id       INT PRIMARY KEY AUTO_INCREMENT,
    mijoz_id INT NOT NULL,
    summa    DECIMAL(10,2) NOT NULL,
    FOREIGN KEY (mijoz_id) REFERENCES mijozlar(id)
);

INSERT INTO mijozlar (ism) VALUES ('Husanboy'), ('Malika');

INSERT INTO buyurtmalar_yumshoq (mijoz_id, summa) VALUES
    (1, 8000000), (1, 150000), (2, 300000);
SQL
-- O'chirish o'rniga belgilash
UPDATE mijozlar SET ochirilgan = '2026-09-10 12:00:00' WHERE id = 1;

-- Faol mijozlar
SELECT ism FROM mijozlar WHERE ochirilgan IS NULL ORDER BY id;
Natija
+--------+
| ism    |
+--------+
| Malika |
+--------+
SQL
-- Buyurtmalar saqlanib qoldi - hisobot to'g'ri ishlaydi
SELECT m.ism, COUNT(b.id) AS buyurtmalar, SUM(b.summa) AS jami
FROM mijozlar m
JOIN buyurtmalar_yumshoq b ON b.mijoz_id = m.id
GROUP BY m.id, m.ism
ORDER BY m.id;
Natija
+----------+-------------+------------+
| ism      | buyurtmalar | jami       |
+----------+-------------+------------+
| Husanboy |           2 | 8150000.00 |
| Malika   |           1 |  300000.00 |
+----------+-------------+------------+
Yumshoq o'chirishning narxi

Yumshoq o'chirish (ochirilgan ustuni) tarixni saqlaydi, lekin har so'rovga shart qo'shadi:

SQL
WHERE ochirilgan IS NULL

Buni bitta joyda unutish - o'chirilgan yozuvning foydalanuvchiga ko'rinishi demakdir.

Muammolar:

MuammoIzoh
Har so'rovda filtrUnutish oson
UNIQUE buziladiO'chirilgan email qayta ishlatilmaydi
Jadval o'sib boradiEski yozuvlar qolaveradi
JOIN murakkablashadiHar tomonda filtr kerak

UNIQUE muammosining yechimi - kompozit UNIQUE:

SQL
UNIQUE (email, ochirilgan)

ochirilgan NULL bo'lgani uchun (11-bo'lim) bir nechta o'chirilgan yozuv bir xil emailga ega bo'la oladi, faol yozuvlar orasida esa email noyob qoladi.

Filtrni unutmaslik uchun ko'rinish (view) yarating:

SQL
CREATE VIEW faol_mijozlar AS
SELECT * FROM mijozlar WHERE ochirilgan IS NULL;

Kod mijozlar o'rniga faol_mijozlar bilan ishlaydi.

Qachon yumshoq o'chirish kerak: moliyaviy hujjatlar, audit talab qilinadigan ma'lumot, tiklash imkoniyati kerak bo'lgan joylar.

Qachon kerak emas: sessiyalar, kesh, vaqtinchalik yozuvlar, jurnal.

Tashqi kalitsiz ishlash #

"Tashqi kalit sekinlashtiradi" - to'g'rimi?

Ba'zi jamoalar tashqi kalitlarni ataylab ishlatmaydi. Sabablari:

SababBaho
"Yozishni sekinlashtiradi"To'g'ri, lekin farq juda kichik
"Migratsiya qiyinlashadi"To'g'ri - vaqtincha o'chirish mumkin
"Bo'laklangan bazada ishlamaydi"To'g'ri - sharding da cheklov
"ORM o'zi tekshiradi"Noto'g'ri - u hamma yo'lni qamramaydi

Birinchi sabab odatda o'lchanmagan. Tashqi kalit tekshiruvi indeks bo'yicha qidiruv - u mikrosekundlarda bajariladi.

To'rtinchi sabab eng xavfli. ORM faqat o'z kodi orqali o'tgan yozuvlarni tekshiradi. Import skripti, admin paneli, qo'lda UPDATE va boshqa xizmatlar uni chetlab o'tadi.

Haqiqiy istisnolar:

  • Juda katta miqyosdagi taqsimlangan tizim (sharding);
  • Analitik ombor (ma'lumot faqat o'qiladi);
  • Vaqtinchalik yuklash jadvallari.

Bu holatlarda ham butunlikni muntazam tekshirish kerak:

SQL
SELECT COUNT(*) AS yetimlar
FROM kitoblar_himoyasiz k
LEFT JOIN mualliflar m ON m.id = k.muallif_id
WHERE m.id IS NULL;

Odatiy veb ilova uchun esa javob oddiy: tashqi kalitlarni qo'ying.

Yetimlarni topish #

SQL
SELECT k.id, k.sarlavha, k.muallif_id
FROM kitoblar_himoyasiz k
LEFT JOIN mualliflar m ON m.id = k.muallif_id
WHERE m.id IS NULL
ORDER BY k.id;
Natija
+----+-------------+------------+
| id | sarlavha    | muallif_id |
+----+-------------+------------+
|  3 | Sirli kitob |        999 |
+----+-------------+------------+
Amaliy topshiriq
  1. Tashqi kalitsiz jadvalga yetim qator kiriting.
  2. LEFT JOIN bilan uni toping.
  3. Tashqi kalit qo'shib, xuddi shu yozuvni rad ettiring.
  4. Bolasi bor otani o'chirishga urinib ko'ring.
  5. ON DELETE CASCADE bilan zanjirli o'chirishni sinang.
  6. CASCADE qachon xavfli ekanini uch misolda ayting.
  7. ON DELETE SET NULL bilan bo'limni o'chiring.
  8. SET NULL uchun ustun qanday bo'lishi kerakligini ayting.
  9. Yumshoq o'chirishni amalga oshiring.
  10. Yumshoq o'chirishning to'rtta muammosini sanang.

Xulosa #

  • Tashqi kalitsiz yetim qatorlar jimgina to'planadi.
  • Xato yozish paytida chiqadi - oylar keyin emas.
  • Standart qoida RESTRICT - eng xavfsiz.
  • CASCADE zanjirli ishlaydi - ehtiyot bo'ling.
  • Bola qator mustaqil qiymatga ega bo'lsa, CASCADE ishlatmang.
  • SET NULL uchun ustun NULL ga ruxsat berishi shart.
  • ON UPDATE sun'iy kalitda deyarli kerak emas.
  • Yumshoq o'chirish tarixni saqlaydi, lekin har so'rovga filtr qo'shadi.
  • UNIQUE muammosini kompozit UNIQUE hal qiladi.
  • ORM tashqi kalitni almashtirmaydi - u hamma yo'lni qamramaydi.
  • Odatiy ilova uchun: tashqi kalitlarni qo'ying.

Keyingi bo'limda indekslar qanday ishlashini ko'ramiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.