9-bo‘lim
Denormalizatsiya
Qoidani ataylab buzish, hisoblangan ustunlar, tarixiy nusxalar, materiallashgan ko'rinishlar va uni ushlab turish narxi.
Ushbu bo‘lim mundarijasi
Normalizatsiya - qoida, denormalizatsiya - ongli istisno. Farqi shundaki, istisno sabab bilan va hujjatlashtirilgan bo'ladi.
Nima uchun qoidani buzish kerak #
Tarixiy nusxa - eng asosli tur #
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);
-- 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;
+----------+------------+---------+---------------+--------------+
| 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 |
+----------+------------+---------+---------------+--------------+
narx_sotilgan mahsulotlar.narx ning nusxasidek ko'rinadi,
lekin aslida butunlay boshqa fakt:
| Ustun | Nima anglatadi |
|---|---|
mahsulotlar.narx | Bugungi narx |
buyurtma_qatorlari.narx_sotilgan | O'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 qiymat | Nima uchun |
|---|---|
| Shartnomadagi manzil | Mijoz ko'chib ketishi mumkin |
| Hisobotdagi valyuta kursi | Kurs har kuni o'zgaradi |
| Hujjatdagi imzolovchi lavozimi | Lavozim o'zgaradi |
| Chekdagi QQS stavkasi | Qonun o'zgaradi |
Bu holatlarda nusxa majburiy, denormalizatsiya emas.
Hisoblangan ustun #
-- 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;
+----+----------+------------+
| id | mijoz | jami |
+----+----------+------------+
| 1 | Husanboy | 8300000.00 |
| 2 | Malika | 8000000.00 |
+----+----------+------------+
Denormallashgan variant - jami ni ustunda saqlash:
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
);
SELECT id, mijoz, jami FROM buyurtmalar ORDER BY id;
+----+----------+------------+
| id | mijoz | jami |
+----+----------+------------+
| 1 | Husanboy | 8300000.00 |
| 2 | Malika | 8000000.00 |
+----+----------+------------+
Endi jami ni har o'zgarishda yangilash kerak:
| Amal | jami yangilanishi kerakmi |
|---|---|
| Yangi qator qo'shildi | Ha |
| Qator o'chirildi | Ha |
soni o'zgardi | Ha |
narx_sotilgan tuzatildi | Ha |
| Buyurtma bekor qilindi | Ha |
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)
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
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:
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 #
-- 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;
+----+------------+------------+
| id | saqlangan | haqiqiy |
+----+------------+------------+
| 1 | 8300000.00 | 8000000.00 |
+----+------------+------------+
Ikki xil haqiqat - aynan normalizatsiya oldini olmoqchi bo'lgan holat.
Generatsiyalangan ustun #
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);
SELECT id, soni, narx, summa FROM qatorlar_avtomatik ORDER BY id;
+----+------+------------+------------+
| id | soni | narx | summa |
+----+------+------------+------------+
| 1 | 3 | 150000.00 | 450000.00 |
| 2 | 1 | 8000000.00 | 8000000.00 |
+----+------+------------+------------+
-- Uni qo'lda o'zgartirib bo'lmaydi
INSERT INTO qatorlar_avtomatik (soni, narx, summa) VALUES (1, 100, 999);
ERROR 1906 (HY000): The value specified for generated column 'summa'
in table 'qatorlar_avtomatik' is not allowed
-- 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;
+----+------+-----------+-----------+
| id | soni | narx | summa |
+----+------+-----------+-----------+
| 1 | 5 | 150000.00 | 750000.00 |
+----+------+-----------+-----------+
STORED va VIRTUAL farqisumma DECIMAL(12,2) AS (soni * narx) STORED -- diskda saqlanadi
summa DECIMAL(12,2) AS (soni * narx) VIRTUAL -- o'qishda hisoblanadi
STORED | VIRTUAL | |
|---|---|---|
| Disk joyi | Egallaydi | Egallamaydi |
| O'qish | Tez | Har safar hisoblanadi |
| Yozish | Sekinroq | Tez |
| Indekslash | Mumkin | MariaDB da mumkin |
Amaliy tanlov:
- Tez-tez o'qiladi, kam yoziladi →
STORED; - 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 #
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;
+--------------+---------------+
| muallif_ismi | matn |
+--------------+---------------+
| Husanboy | Ajoyib maqola |
| Husanboy | Rahmat |
| Malika | Foydali |
+--------------+---------------+
Foydalanuvchi ismini o'zgartirsa, barcha izohlarni yangilash kerak:
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:
| Shart | Izoh |
|---|---|
| Qiymat deyarli o'zgarmaydi | Ism kamdan-kam o'zgaradi |
| O'qish juda ko'p | Izohlar millionlab marta ko'riladi |
JOIN isbotlangan to'siq | Siz o'lchagansiz |
| Yangilash markazlashgan | Bitta 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 #
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;
+------------+-------------+------------+
| sana | buyurtmalar | jami_summa |
+------------+-------------+------------+
| 2026-01-10 | 1 | 8300000.00 |
| 2026-03-15 | 1 | 8000000.00 |
+------------+-------------+------------+
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.
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 #
| Qadam | Savol |
|---|---|
| 1 | Sxema 3NF dami? |
| 2 | Kerakli indekslar qo'yilganmi? |
| 3 | So'rov haqiqatan sekinmi? O'lchadingizmi? |
| 4 | Kesh yordam bermaydimi? |
| 5 | Faqat shundan keyin - denormalizatsiya |
Denormalizatsiya qilsangiz, uchta narsani bajaring:
1. Sababni yozing
-- 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.
- Buyurtmada sotilgan narxni saqlab, mahsulot narxini o'zgartiring.
- Tarixiy nusxa nima uchun denormalizatsiya emasligini tushuntiring.
jamiustunini qo'shib, uni hisoblang.- Qatorni o'chirib,
jamibilan ziddiyat hosil qiling. - Nomuvofiqlikni topuvchi tekshiruv so'rovini yozing.
- Generatsiyalangan
STOREDustun yarating. - Uni qo'lda o'zgartirishga urinib ko'ring.
STOREDvaVIRTUALfarqini ayting.- Nusxalangan ism o'zgarganda muammoni tushuntiring.
- 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.
yangilanganustunini 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.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.