Query optimizatsiya — N+1, join strategiyalari, qoidalar
Foydalanuvchilar ro'yxati yuklandi — 100 foydalanuvchi. Har birining oxirgi buyurtmasini ko'rsatish kerak. Kod:
const users = await db.query('SELECT * FROM foydalanuvchilar');
for (const user of users) {
const order = await db.query(
'SELECT * FROM buyurtmalar WHERE foydalanuvchi_id = $1 LIMIT 1',
[user.id]
);
}
Bu 101 ta so'rov — 1 ta foydalanuvchilar uchun, 100 ta har biri uchun. Bu N+1 muammo.
N+1 muammo
N ta yozuv uchun N ta qo'shimcha so'rov — jadval o'sgan sari so'rovlar ham o'sadi.
Yechim — bitta JOIN:
SELECT
f.id, f.ism,
b.mahsulot AS oxirgi_buyurtma
FROM foydalanuvchilar f
LEFT JOIN LATERAL (
SELECT mahsulot FROM buyurtmalar
WHERE foydalanuvchi_id = f.id
ORDER BY sana DESC
LIMIT 1
) b ON TRUE;
101 ta o'rniga — 1 ta so'rov.
SELECT * — keraksiz ustunlar
-- ❌ — barcha ustunlar, kerakmaydiganlar ham
SELECT * FROM foydalanuvchilar;
-- ✅ — faqat keraklisi
SELECT id, ism, email FROM foydalanuvchilar;
Katta jadvallarda SELECT * ko'p ma'lumot uzatadi — tarmoq va xotira sarflanadi.
WHERE da indekssiz funksiya
-- ❌ — index ishlamaydi
WHERE EXTRACT(YEAR FROM yaratilgan) = 2024;
-- ✅ — range bilan, index ishlaydi
WHERE yaratilgan BETWEEN '2024-01-01' AND '2024-12-31';
-- ❌
WHERE LOWER(email) = 'jasur@mail.com';
-- ✅ variantlar:
-- 1. Functional index yaratish
CREATE INDEX ON foydalanuvchilar (LOWER(email));
-- 2. ILIKE ishlatish (kichik jadvalda)
WHERE email ILIKE 'jasur@mail.com';
JOIN tartibini optimallashtirish
PostgreSQL JOIN tartibini o'zi tanlaydi — lekin ba'zan yordam kerak:
-- Kichik jadvaldan boshlash yaxshi
SELECT *
FROM kichik_jadval k
JOIN katta_jadval t ON k.id = t.kichik_id
WHERE k.tur = 'A';
WHERE sharti avval kichik jadvalga qo'llanadi — natija kichik bo'ladi, JOIN dan o'tishi tez.
LIMIT erta qo'yish
-- ❌ — million qator JOIN qilinadi, keyin 10 ta olinadi
SELECT f.ism, b.mahsulot
FROM foydalanuvchilar f
JOIN buyurtmalar b ON f.id = b.foydalanuvchi_id
LIMIT 10;
-- ✅ — avval 10 ta foydalanuvchi, keyin JOIN
SELECT f.ism, b.mahsulot
FROM (SELECT * FROM foydalanuvchilar LIMIT 10) f
JOIN buyurtmalar b ON f.id = b.foydalanuvchi_id;
COUNT(*) vs COUNT(ustun)
COUNT(*) -- barcha qatorlar, NULL ham
COUNT(telefon) -- faqat NULL bo'lmagan telefon qatorlar
COUNT(DISTINCT email) -- noyob emaillar
COUNT(*) eng tez — biror ustunni tekshirmaydi.
Optimizatsiya qoidalari
1. N+1 muammosidan qoching — JOIN bilan bitta so'rov
2. SELECT * ishlatmang — faqat kerakli ustunlar
3. INDEX qo'llang — WHERE, JOIN, ORDER BY ustunlariga
4. WHERE da funksiya ehtiyotkorlik — index ishlamasligi mumkin
5. LIMIT erta qo'ying — kerakli qatorlarda JOIN
6. EXPLAIN ANALYZE — sekin so'rovni tekshiring
7. ANALYZE — statistikani yangilang
Tez-tez uchraydigan xatolar
OR bilan index:
WHERE id = 1 OR id = 2 OR id = 3
-- ✅ IN bilan — index yaxshiroq ishlaydi
WHERE id IN (1, 2, 3)
NOT IN da NULL:
WHERE id NOT IN (SELECT foydalanuvchi_id FROM bloklangan);
-- bloklangan da NULL bo'lsa — hech nima qaytmaydi!
-- ✅ NOT EXISTS bilan
WHERE NOT EXISTS (
SELECT 1 FROM bloklangan WHERE foydalanuvchi_id = f.id
);
Xulosa
N+1 muammo → JOIN bilan bitta so'rovga birlashtirish
SELECT * → faqat kerakli ustunlar
INDEX → WHERE, JOIN ON, ORDER BY ustunlariga
WHERE funksiya → range yoki functional index
LIMIT → JOIN dan oldin
EXPLAIN ANALYZE — sekin so'rovni tekshiring
ANALYZE jadval — statistikani yangilang
Keyingi dars
41-dars: VACUUM, ANALYZE, AUTOVACUUM — nima uchun kerak