9-bo‘lim

Denormalizatsiya

Qoidani ataylab buzish, hisoblangan ustunlar, tarixiy nusxalar, materiallashgan ko'rinishlar va uni ushlab turish narxi.

🕑 18 daqiqa o‘qish 📄 980 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Nima uchun qoidani buzish kerak
  2. Tarixiy nusxa - eng asosli tur
  3. Hisoblangan ustun
  4. Ziddiyat qanday paydo bo'ladi
  5. Generatsiyalangan ustun
  6. Nusxalangan ustun bilan JOIN ni kamaytirish
  7. Materiallashgan hisobot jadvali
  8. Qaror qabul qilish tartibi
  9. Xulosa

Normalizatsiya - qoida, denormalizatsiya - ongli istisno. Farqi shundaki, istisno sabab bilan va hujjatlashtirilgan bo'ladi.

Nima uchun qoidani buzish kerak #

Muvozanat Normallashgan + Takrorlanish yo'q + Yangilash arzon va xavfsiz + Ziddiyat mumkin emas + Joy tejaladi - Ko'p JOIN kerak - Agregatsiya sekin bo'lishi mumkin Denormallashgan + O'qish tez + JOIN kam - Takrorlanish bor - Yangilash qimmat - Ziddiyat XAVFI bor - Sinxronlash KODDA Denormalizatsiya - o'qish tezligini yozish murakkabligiga almashtirish
Bu bepul emas - narxini oldindan bilib turing

Tarixiy nusxa - eng asosli tur #

SQL
CREATE TABLE mahsulotlar (
    id   INT PRIMARY KEY AUTO_INCREMENT,
    nom  VARCHAR(100) NOT NULL,
    narx DECIMAL(10,2) NOT NULL
);

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

CREATE TABLE buyurtma_qatorlari (
    buyurtma_id  INT NOT NULL,
    mahsulot_id  INT NOT NULL,
    soni         INT NOT NULL,
    narx_sotilgan DECIMAL(10,2) NOT NULL,   -- ATAYLAB nusxalangan
    PRIMARY KEY (buyurtma_id, mahsulot_id),
    FOREIGN KEY (buyurtma_id) REFERENCES buyurtmalar(id),
    FOREIGN KEY (mahsulot_id) REFERENCES mahsulotlar(id)
);

INSERT INTO mahsulotlar (nom, narx) VALUES
    ('Noutbuk', 8000000), ('Sichqoncha', 150000);

INSERT INTO buyurtmalar (mijoz, sana) VALUES
    ('Husanboy', '2026-01-10'), ('Malika', '2026-03-15');

INSERT INTO buyurtma_qatorlari VALUES
    (1, 1, 1, 8000000),
    (1, 2, 2, 150000),
    (2, 1, 1, 8000000);
SQL
-- Mahsulot narxi ko'tarildi
UPDATE mahsulotlar SET narx = 9500000 WHERE id = 1;

SELECT b.id AS buyurtma, b.sana, m.nom,
       q.narx_sotilgan AS sotilgan_narx,
       m.narx AS bugungi_narx
FROM buyurtma_qatorlari q
JOIN buyurtmalar b  ON b.id = q.buyurtma_id
JOIN mahsulotlar m  ON m.id = q.mahsulot_id
WHERE m.id = 1
ORDER BY b.id;
Natija
+----------+------------+---------+---------------+--------------+
| buyurtma | sana       | nom     | sotilgan_narx | bugungi_narx |
+----------+------------+---------+---------------+--------------+
|        1 | 2026-01-10 | Noutbuk |    8000000.00 |   9500000.00 |
|        2 | 2026-03-15 | Noutbuk |    8000000.00 |   9500000.00 |
+----------+------------+---------+---------------+--------------+
Bu denormalizatsiya emas - bu turli faktlar

narx_sotilgan mahsulotlar.narx ning nusxasidek ko'rinadi, lekin aslida butunlay boshqa fakt:

UstunNima anglatadi
mahsulotlar.narxBugungi narx
buyurtma_qatorlari.narx_sotilganO'sha kundagi narx

Ular tasodifan bir xil bo'lishi mumkin, lekin bu bir xil ma'lumot emas.

Agar narx_sotilgan bo'lmasa, eski hisobotlar noto'g'ri bo'lardi - kecha 8 mln ga sotilgan noutbuk bugun 9.5 mln ga sotilgan deb ko'rinardi.

Xuddi shu naqsh:

Nusxalangan qiymatNima uchun
Shartnomadagi manzilMijoz ko'chib ketishi mumkin
Hisobotdagi valyuta kursiKurs har kuni o'zgaradi
Hujjatdagi imzolovchi lavozimiLavozim o'zgaradi
Chekdagi QQS stavkasiQonun o'zgaradi

Bu holatlarda nusxa majburiy, denormalizatsiya emas.

Hisoblangan ustun #

SQL
-- Har safar hisoblash
SELECT b.id, b.mijoz, SUM(q.soni * q.narx_sotilgan) AS jami
FROM buyurtmalar b
JOIN buyurtma_qatorlari q ON q.buyurtma_id = b.id
GROUP BY b.id, b.mijoz
ORDER BY b.id;
Natija
+----+----------+------------+
| id | mijoz    | jami       |
+----+----------+------------+
|  1 | Husanboy | 8300000.00 |
|  2 | Malika   | 8000000.00 |
+----+----------+------------+

Denormallashgan variant - jami ni ustunda saqlash:

SQL
ALTER TABLE buyurtmalar ADD COLUMN jami DECIMAL(12,2) NOT NULL DEFAULT 0;

UPDATE buyurtmalar b
SET jami = (
    SELECT COALESCE(SUM(q.soni * q.narx_sotilgan), 0)
    FROM buyurtma_qatorlari q
    WHERE q.buyurtma_id = b.id
);
SQL
SELECT id, mijoz, jami FROM buyurtmalar ORDER BY id;
Natija
+----+----------+------------+
| id | mijoz    | jami       |
+----+----------+------------+
|  1 | Husanboy | 8300000.00 |
|  2 | Malika   | 8000000.00 |
+----+----------+------------+
Hisoblangan ustun - eng ko'p buziladigan joy

Endi jami ni har o'zgarishda yangilash kerak:

Amaljami yangilanishi kerakmi
Yangi qator qo'shildiHa
Qator o'chirildiHa
soni o'zgardiHa
narx_sotilgan tuzatildiHa
Buyurtma bekor qilindiHa

Bittasini unutsangiz - jami jimgina noto'g'ri bo'lib qoladi. Buni hech kim sezmaydi, toki hisobot mos kelmaguncha.

Uch himoya usuli bor:

1. Generatsiyalangan ustun (eng xavfsiz, lekin cheklangan)

SQL
jami DECIMAL(12,2) AS (soni * narx) STORED

Ma'lumotlar bazasi o'zi hisoblaydi - buzib bo'lmaydi. Lekin faqat bir qatordagi ustunlardan hisoblanadi, agregatsiya mumkin emas.

2. Trigger

SQL
CREATE TRIGGER ... AFTER INSERT ON buyurtma_qatorlari ...

Ishonchli, lekin ko'rinmaydi - keyingi dasturchi nima uchun qiymat o'zgarayotganini tushunmasligi mumkin.

3. Ilova kodida

Eng ko'p ishlatiladi va eng xavfli - har yozish yo'lini eslash kerak.

Qaysi usulni tanlasangiz ham, muntazam tekshiruv yozing:

SQL
SELECT b.id FROM buyurtmalar b
WHERE b.jami <> (SELECT COALESCE(SUM(soni * narx_sotilgan), 0)
                 FROM buyurtma_qatorlari WHERE buyurtma_id = b.id);

Bu so'rov bo'sh qaytarishi kerak. Uni kechasi ishlaydigan vazifaga qo'ying.

Ziddiyat qanday paydo bo'ladi #

SQL
-- Dasturchi qatorni o'chirdi, lekin jami ni yangilashni unutdi
DELETE FROM buyurtma_qatorlari WHERE buyurtma_id = 1 AND mahsulot_id = 2;

SELECT b.id, b.jami AS saqlangan,
       COALESCE(SUM(q.soni * q.narx_sotilgan), 0) AS haqiqiy
FROM buyurtmalar b
LEFT JOIN buyurtma_qatorlari q ON q.buyurtma_id = b.id
WHERE b.id = 1
GROUP BY b.id, b.jami;
Natija
+----+------------+------------+
| id | saqlangan  | haqiqiy    |
+----+------------+------------+
|  1 | 8300000.00 | 8000000.00 |
+----+------------+------------+

Ikki xil haqiqat - aynan normalizatsiya oldini olmoqchi bo'lgan holat.

Generatsiyalangan ustun #

SQL
CREATE TABLE qatorlar_avtomatik (
    id           INT PRIMARY KEY AUTO_INCREMENT,
    soni         INT NOT NULL,
    narx         DECIMAL(10,2) NOT NULL,
    summa        DECIMAL(12,2) AS (soni * narx) STORED
);

INSERT INTO qatorlar_avtomatik (soni, narx) VALUES (3, 150000), (1, 8000000);
SQL
SELECT id, soni, narx, summa FROM qatorlar_avtomatik ORDER BY id;
Natija
+----+------+------------+------------+
| id | soni | narx       | summa      |
+----+------+------------+------------+
|  1 |    3 |  150000.00 |  450000.00 |
|  2 |    1 | 8000000.00 | 8000000.00 |
+----+------+------------+------------+
SQL
-- Uni qo'lda o'zgartirib bo'lmaydi
INSERT INTO qatorlar_avtomatik (soni, narx, summa) VALUES (1, 100, 999);
Natija
ERROR 1906 (HY000): The value specified for generated column 'summa'
in table 'qatorlar_avtomatik' is not allowed
SQL
-- soni o'zgarsa, summa AVTOMATIK yangilanadi
UPDATE qatorlar_avtomatik SET soni = 5 WHERE id = 1;

SELECT id, soni, narx, summa FROM qatorlar_avtomatik WHERE id = 1;
Natija
+----+------+-----------+-----------+
| id | soni | narx      | summa     |
+----+------+-----------+-----------+
|  1 |    5 | 150000.00 | 750000.00 |
+----+------+-----------+-----------+
STORED va VIRTUAL farqi
SQL
summa DECIMAL(12,2) AS (soni * narx) STORED    -- diskda saqlanadi
summa DECIMAL(12,2) AS (soni * narx) VIRTUAL   -- o'qishda hisoblanadi
STOREDVIRTUAL
Disk joyiEgallaydiEgallamaydi
O'qishTezHar safar hisoblanadi
YozishSekinroqTez
IndekslashMumkinMariaDB da mumkin

Amaliy tanlov:

  • Tez-tez o'qiladi, kam yoziladiSTORED;
  • Kam o'qiladi yoki hisob arzon → VIRTUAL.

Generatsiyalangan ustunning kuchli tomoni - uni buzib bo'lmaydi. Agar denormalizatsiya bitta qator ichidagi hisobdan iborat bo'lsa, har doim shuni tanlang.

Cheklovi: faqat shu qatordagi ustunlarni ishlata oladi. Boshqa jadvaldan SUM olish mumkin emas - unda trigger yoki ilova kodi kerak.

Nusxalangan ustun bilan JOIN ni kamaytirish #

SQL
CREATE TABLE izohlar (
    id             INT PRIMARY KEY AUTO_INCREMENT,
    matn           TEXT NOT NULL,
    muallif_id     INT NOT NULL,
    muallif_ismi   VARCHAR(100) NOT NULL,   -- nusxalangan
    yaratilgan     DATETIME NOT NULL
);

INSERT INTO izohlar (matn, muallif_id, muallif_ismi, yaratilgan) VALUES
    ('Ajoyib maqola', 1, 'Husanboy', '2026-01-10 10:00:00'),
    ('Rahmat',        1, 'Husanboy', '2026-01-11 11:00:00'),
    ('Foydali',       2, 'Malika',   '2026-01-12 12:00:00');

-- JOIN kerak emas
SELECT muallif_ismi, matn FROM izohlar ORDER BY id;
Natija
+--------------+---------------+
| muallif_ismi | matn          |
+--------------+---------------+
| Husanboy     | Ajoyib maqola |
| Husanboy     | Rahmat        |
| Malika       | Foydali       |
+--------------+---------------+
Ism o'zgarganda nima bo'ladi?

Foydalanuvchi ismini o'zgartirsa, barcha izohlarni yangilash kerak:

SQL
UPDATE izohlar SET muallif_ismi = 'Yangi ism' WHERE muallif_id = 1;

Bir foydalanuvchida 10 000 izoh bo'lsa - bu og'ir amal.

Va agar bittasi o'tkazib yuborilsa, bir foydalanuvchi ikki xil nom bilan ko'rinadi.

Qachon bu maqbul:

ShartIzoh
Qiymat deyarli o'zgarmaydiIsm kamdan-kam o'zgaradi
O'qish juda ko'pIzohlar millionlab marta ko'riladi
JOIN isbotlangan to'siqSiz o'lchagansiz
Yangilash markazlashganBitta joyda hal qilinadi

Qachon maqbul emas:

  • Qiymat tez-tez o'zgarsa (holat, qoldiq, reyting);
  • Aniqlik muhim bo'lsa (moliyaviy ma'lumot);
  • Hali o'lchamagan bo'lsangiz.

Oxirgisi eng muhim: avval JOIN bilan yozing va o'lchang. Zamonaviy ma'lumotlar bazasi indeksli JOIN ni juda tez bajaradi - ko'p hollarda muammo umuman bo'lmaydi.

Materiallashgan hisobot jadvali #

SQL
CREATE TABLE kunlik_hisobot (
    sana            DATE PRIMARY KEY,
    buyurtmalar     INT NOT NULL,
    jami_summa      DECIMAL(14,2) NOT NULL,
    yangilangan     DATETIME NOT NULL
);

INSERT INTO kunlik_hisobot (sana, buyurtmalar, jami_summa, yangilangan)
SELECT b.sana,
       COUNT(DISTINCT b.id),
       COALESCE(SUM(q.soni * q.narx_sotilgan), 0),
       '2026-03-16 03:00:00'
FROM buyurtmalar b
LEFT JOIN buyurtma_qatorlari q ON q.buyurtma_id = b.id
GROUP BY b.sana;

SELECT sana, buyurtmalar, jami_summa FROM kunlik_hisobot ORDER BY sana;
Natija
+------------+-------------+------------+
| sana       | buyurtmalar | jami_summa |
+------------+-------------+------------+
| 2026-01-10 |           1 | 8300000.00 |
| 2026-03-15 |           1 | 8000000.00 |
+------------+-------------+------------+
Hisobot jadvallari - eng xavfsiz denormalizatsiya

Bu naqsh boshqalaridan xavfsizroq, chunki:

  • Hisobot jadvali asosiy ma'lumot emas - u nusxa;
  • U butunlay qayta qurilishi mumkin;
  • Ziddiyat bo'lsa - qayta hisoblanadi, ma'lumot yo'qolmaydi.
SQL
TRUNCATE kunlik_hisobot;
INSERT INTO kunlik_hisobot SELECT ...;

Odatda kechasi ishlaydigan vazifa orqali yangilanadi.

yangilangan ustunini albatta qo'shing - foydalanuvchi ma'lumot qanchalik eski ekanini bilishi kerak:

"Hisobot 2026-03-16 03:00 holatiga ko'ra"

PostgreSQL da bunday jadval uchun tayyor mexanizm bor (MATERIALIZED VIEW), MySQL va MariaDB da esa uni qo'lda tuzasiz.

Katta tizimlarda bu alohida analitik bazaga ajratiladi - shunda hisobotlar asosiy bazani sekinlashtirmaydi.

Qaror qabul qilish tartibi #

Denormalizatsiyadan oldin
QadamSavol
1Sxema 3NF dami?
2Kerakli indekslar qo'yilganmi?
3So'rov haqiqatan sekinmi? O'lchadingizmi?
4Kesh yordam bermaydimi?
5Faqat shundan keyin - denormalizatsiya

Denormalizatsiya qilsangiz, uchta narsani bajaring:

1. Sababni yozing

SQL
-- Denormalizatsiya: muallif_ismi nusxalangan.
-- Sabab: izohlar sahifasi kuniga ~2 mln marta ochiladi,
-- JOIN o'rtacha 40 ms qo'shardi (2026-01 o'lchovi).
-- Yangilash: foydalanuvchi profili saqlanganda.
muallif_ismi VARCHAR(100) NOT NULL,

2. Sinxronlash joyini bitta qiling

Hamma yozish yo'li bitta funksiyadan o'tsin. Har joyda qo'lda yangilash - ziddiyatning kafolatlangan yo'li.

3. Tekshiruv yozing

Muntazam ishlaydigan so'rov nomuvofiqlikni topsin va ogohlantirsin.

Bu uchtasiz denormalizatsiya - kelajakdagi xato, optimallashtirish emas.

Amaliy topshiriq
  1. Buyurtmada sotilgan narxni saqlab, mahsulot narxini o'zgartiring.
  2. Tarixiy nusxa nima uchun denormalizatsiya emasligini tushuntiring.
  3. jami ustunini qo'shib, uni hisoblang.
  4. Qatorni o'chirib, jami bilan ziddiyat hosil qiling.
  5. Nomuvofiqlikni topuvchi tekshiruv so'rovini yozing.
  6. Generatsiyalangan STORED ustun yarating.
  7. Uni qo'lda o'zgartirishga urinib ko'ring.
  8. STORED va VIRTUAL farqini ayting.
  9. Nusxalangan ism o'zgarganda muammoni tushuntiring.
  10. Denormalizatsiyadan oldingi besh qadamni sanang.

Xulosa #

  • Denormalizatsiya - o'qish tezligini yozish murakkabligiga almashtirish.
  • Tarixiy nusxa (sotilgan narx) denormalizatsiya emas - bu boshqa fakt.
  • Hisoblangan ustun har o'zgarishda yangilanishi kerak.
  • Bittasini unutish - jimgina noto'g'ri ma'lumot.
  • Generatsiyalangan ustun (STORED) - eng xavfsiz variant.
  • U faqat bitta qator ichida ishlaydi.
  • Nusxalangan ustun kamdan-kam o'zgaradigan qiymatlar uchun.
  • Hisobot jadvallari - eng xavfsiz tur, qayta qurilishi mumkin.
  • yangilangan ustunini albatta qo'shing.
  • Avval 3NF, indeks, o'lchov, kesh - keyin denormalizatsiya.
  • Sabab, bitta sinxronlash joyi va tekshiruv - uchalasi shart.

Keyingi bo'limda ma'lumot turlarini to'g'ri tanlashni 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.