14-bo‘lim

Indekslar qanday ishlaydi

B-daraxt tuzilmasi, klasterlangan va ikkilamchi indeks, kompozit indeks tartibi, qamrovchi indeks va indeksning narxi.

🕑 20 daqiqa o‘qish 📄 1 205 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Indekssiz qidiruv
  2. Indeks bilan
  3. B-daraxt tuzilmasi
  4. Klasterlangan indeks
  5. Kompozit indeks va ustunlar tartibi
  6. Qamrovchi indeks
  7. Indeksning narxi
  8. Qaysi ustunga indeks kerak
  9. Selektivlikni o'lchash
  10. Xulosa

Indeks - kitobning mundarijasi. Usiz har savolga javob topish uchun butun kitobni varaqlash kerak.

Indekssiz qidiruv #

SQL
CREATE TABLE sonlar (
    n INT PRIMARY KEY
);

INSERT INTO sonlar (n)
WITH RECURSIVE ketma_ket AS (
    SELECT 1 AS n
    UNION ALL
    SELECT n + 1 FROM ketma_ket WHERE n < 20000
)
SELECT n FROM ketma_ket;

CREATE TABLE xodimlar (
    id       INT PRIMARY KEY,
    ism      VARCHAR(50)  NOT NULL,
    familiya VARCHAR(50)  NOT NULL,
    bolim    VARCHAR(30)  NOT NULL,
    maosh    INT          NOT NULL,
    shahar   VARCHAR(30)  NOT NULL
);

INSERT INTO xodimlar (id, ism, familiya, bolim, maosh, shahar)
SELECT
    n,
    CONCAT('Ism', n),
    CONCAT('Familiya', n),
    ELT(1 + (n % 4), 'IT', 'Hisobot', 'Marketing', 'Kadrlar'),
    3000000 + (n % 50) * 100000,
    ELT(1 + (n % 5), 'Namangan', 'Toshkent', 'Andijon', 'Farg''ona', 'Buxoro')
FROM sonlar;
SQL
SELECT COUNT(*) AS qatorlar FROM xodimlar;
Natija
+----------+
| qatorlar |
+----------+
|    20000 |
+----------+
SQL
-- Indekssiz ustun bo'yicha qidiruv - butun jadval o'qiladi
FLUSH STATUS;

SELECT COUNT(*) FROM xodimlar WHERE familiya = 'Familiya500';

SHOW SESSION STATUS LIKE 'Handler_read_rnd_next';
Natija
+----------+
| COUNT(*) |
+----------+
|        1 |
+----------+
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_rnd_next | 20001 |
+-----------------------+-------+
Handler_read_rnd_next - to'liq skanerlash o'lchagichi

Bu hisoblagich ketma-ket o'qilgan qatorlar sonini ko'rsatadi - ya'ni indekssiz varaqlangan qatorlar.

20001 - butun jadval (20 000 qator) plus oxirgi tekshiruv. Ma'lumotlar bazasi bitta qatorni topish uchun hammasini o'qidi.

Foydali hisoblagichlar:

HisoblagichNima anglatadi
Handler_read_rnd_nextTo'liq skanerlash - kam bo'lgani yaxshi
Handler_read_keyIndeks bo'yicha qidiruv - ko'p bo'lgani yaxshi
Handler_read_nextIndeks diapazoni bo'yicha o'qish

FLUSH STATUS ularni nolga qaytaradi, shuning uchun bitta so'rovni aniq o'lchash mumkin.

Bu EXPLAIN dan aniqroq: EXPLAIN rejani ko'rsatadi, hisoblagichlar esa haqiqatan nima bo'lganini.

Indeks bilan #

SQL
CREATE INDEX idx_familiya ON xodimlar (familiya);
SQL
FLUSH STATUS;

SELECT COUNT(*) FROM xodimlar WHERE familiya = 'Familiya500';

SHOW SESSION STATUS LIKE 'Handler_read_rnd_next';
Natija
+----------+
| COUNT(*) |
+----------+
|        1 |
+----------+
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_rnd_next | 0     |
+-----------------------+-------+

Nol qator ketma-ket o'qildi - indeks to'g'ridan-to'g'ri kerakli joyga olib bordi.

B-daraxt tuzilmasi #

B+ daraxt indeksi 50 | 100 10 | 25 60 | 80 120 | 150 1..9 qator 10..24 qator 25..49 qator 50..59 qator 60..79 qator 80.. qator Barglar bir-biriga bog'langan - diapazon bo'yicha o'qish tez 20 000 qator uchun atigi 2-3 daraja - 3 ta o'qish yetarli
Har daraja qidiruv maydonini keskin toraytiradi
Nima uchun indeks shunchalik tez

B+ daraxt balanslangan - har bargga yo'l bir xil uzunlikda.

Har tugunda yuzlab kalit bo'ladi (bitta 16 KB sahifaga sig'adigancha), shuning uchun daraxt juda past bo'ladi:

QatorlarDaraxt darajasiDisk o'qishlari
1 00022
1 000 00033
1 000 000 0004-54-5

Milliard qatorda ham atigi 5 ta o'qish. To'liq skanerlashda esa million marta ko'p.

Yana bir muhim xossa: barglar tartiblangan va bir-biriga bog'langan. Shuning uchun indeks quyidagilarni ham tezlashtiradi:

  • WHERE ustun BETWEEN a AND b - diapazon;
  • ORDER BY ustun - saralash bepul;
  • MIN(ustun), MAX(ustun) - chekka barg;
  • WHERE ustun > x LIMIT 10 - dastlabki qatorlar.

Klasterlangan indeks #

SQL
SELECT INDEX_NAME AS indeks, SEQ_IN_INDEX AS tartib,
       COLUMN_NAME AS ustun, NON_UNIQUE AS takrorlanadi
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'xodimlar'
ORDER BY BINARY INDEX_NAME, SEQ_IN_INDEX;
Natija
+--------------+--------+----------+--------------+
| indeks       | tartib | ustun    | takrorlanadi |
+--------------+--------+----------+--------------+
| PRIMARY      |      1 | id       |            0 |
| idx_familiya |      1 | familiya |            1 |
+--------------+--------+----------+--------------+
InnoDB da birlamchi kalit - qatorning o'zi

InnoDB da jadval birlamchi kalit bo'yicha tartiblangan B-daraxt sifatida saqlanadi. Ya'ni:

Natija
PRIMARY indeks barglari = QATORNING O'ZI

Bu klasterlangan indeks deb ataladi va muhim oqibatlari bor:

1. Birlamchi kalit bo'yicha qidiruv eng tez - qo'shimcha o'qish kerak emas.

2. Ikkilamchi indeks birlamchi kalitni saqlaydi:

Natija
idx_familiya barglari:  ('Familiya500' -> id=500)

Keyin id bo'yicha asosiy daraxtga borish kerak. Bu ikki bosqichli qidiruv (bookmark lookup).

3. Uzun birlamchi kalit hamma indeksni kattalashtiradi. Shuning uchun 3-bo'limda CHAR(36) UUID emas, BINARY(16) tavsiya qilingan edi.

4. Tasodifiy birlamchi kalit yozishni sekinlashtiradi - yangi qator daraxtning o'rtasiga tushadi va sahifa bo'linadi.

Shuning uchun AUTO_INCREMENT yaxshi: yangi qator har doim oxiriga qo'shiladi.

Kompozit indeks va ustunlar tartibi #

SQL
CREATE INDEX idx_bolim_maosh ON xodimlar (bolim, maosh);
SQL
-- Birinchi ustun bo'yicha - indeks ISHLAYDI
FLUSH STATUS;
SELECT COUNT(*) FROM xodimlar WHERE bolim = 'IT';
SHOW SESSION STATUS LIKE 'Handler_read_rnd_next';
Natija
+----------+
| COUNT(*) |
+----------+
|     5000 |
+----------+
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_rnd_next | 0     |
+-----------------------+-------+
SQL
-- Ikkala ustun bo'yicha - indeks TO'LIQ ishlaydi
FLUSH STATUS;
SELECT COUNT(*) FROM xodimlar WHERE bolim = 'IT' AND maosh = 3000000;
SHOW SESSION STATUS LIKE 'Handler_read_rnd_next';
Natija
+----------+
| COUNT(*) |
+----------+
|      200 |
+----------+
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_rnd_next | 0     |
+-----------------------+-------+
SQL
-- FAQAT ikkinchi ustun bo'yicha - indeks YARAMAYDI
FLUSH STATUS;
SELECT COUNT(*) FROM xodimlar WHERE maosh = 3000000;
SHOW SESSION STATUS LIKE 'Handler_read_rnd_next';
Natija
+----------+
| COUNT(*) |
+----------+
|      400 |
+----------+
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_rnd_next | 0     |
+-----------------------+-------+
Chapdan prefiks qoidasi

Kompozit indeks (a, b, c) quyidagi so'rovlarga yordam beradi:

So'rovIndeks ishlaydimi
WHERE a = ?Ha
WHERE a = ? AND b = ?Ha
WHERE a = ? AND b = ? AND c = ?Ha
WHERE b = ?Yo'q
WHERE c = ?Yo'q
WHERE b = ? AND c = ?Yo'q
WHERE a = ? AND c = ?Faqat a qismi

Buni telefon kitobi bilan tasavvur qiling: u (familiya, ism) bo'yicha tartiblangan.

  • "Qodirov" ni topish - oson;
  • "Qodirov Husanboy" ni topish - oson;
  • Faqat "Husanboy" ni topish - butun kitobni varaqlash.

Yuqoridagi uchinchi misolda Handler_read_rnd_next nol chiqdi, chunki MariaDB indeksni to'liq skanerlash (index scan) usulini tanladi - u jadvalni skanerlashdan arzonroq, lekin haqiqiy qidiruvdan ancha sekin.

Shuning uchun maosh bo'yicha tez-tez qidirsangiz, unga alohida indeks kerak.

Tartib qanday tanlanadi: eng ko'p tenglik bilan ishlatiladigan ustun birinchi bo'lsin. Diapazon shartlari (>, BETWEEN) esa oxirida - ulardan keyingi ustunlar indeksda ishlamaydi.

Qamrovchi indeks #

SQL
CREATE INDEX idx_shahar_ism ON xodimlar (shahar, ism);
SQL
-- Kerakli ustunlar indeksda BOR - jadvalga borish shart emas
EXPLAIN SELECT shahar, ism FROM xodimlar WHERE shahar = 'Andijon' LIMIT 5;
Natija
+------+-------------+----------+------+----------------+----------------+---------+-------+------+--------------------------+
| id   | select_type | table    | type | possible_keys  | key            | key_len | ref   | rows | Extra                    |
+------+-------------+----------+------+----------------+----------------+---------+-------+------+--------------------------+
|    1 | SIMPLE      | xodimlar | ref  | idx_shahar_ism | idx_shahar_ism | 122     | const | 4000 | Using where; Using index |
+------+-------------+----------+------+----------------+----------------+---------+-------+------+--------------------------+
Using index - eng yaxshi belgi

Extra ustunidagi Using index "qamrovchi indeks" (covering index) degani: so'rovga kerakli barcha ustun indeksning o'zida bor, shuning uchun asosiy jadvalga umuman borilmadi.

Bu ikki bosqichli qidiruvni bir bosqichga aylantiradi va so'rovni sezilarli tezlashtiradi.

Chalkashtirmang:

Extra qiymatiMa'nosi
Using indexQamrovchi indeks - juda yaxshi
Using whereQo'shimcha filtr qo'llandi - normal
Using index conditionFiltr indeks darajasida - yaxshi
Using filesortSaralash uchun qo'shimcha ish - yomon
Using temporaryVaqtinchalik jadval - yomon

Qamrovchi indeks yaratish uchun SELECT dagi ustunlarni ham indeksga qo'shing:

SQL
-- WHERE shahar = ? bo'yicha ism va maosh kerak bo'lsa
CREATE INDEX idx_qamrovchi ON xodimlar (shahar, ism, maosh);

Lekin haddan oshmang - har qo'shimcha ustun indeksni kattalashtiradi va yozishni sekinlashtiradi.

Indeksning narxi #

SQL
SELECT
    COUNT(DISTINCT INDEX_NAME) AS indekslar_soni
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'xodimlar';
Natija
+----------------+
| indekslar_soni |
+----------------+
|              4 |
+----------------+
Har indeks yozishni sekinlashtiradi

Indeks bepul emas:

AmalTa'siri
INSERTHar indeksga yangi yozuv qo'shiladi
UPDATEO'zgargan ustunning indekslari yangilanadi
DELETEHar indeksdan yozuv olib tashlanadi
DiskIndeks joy egallaydi (ba'zan jadvaldan ko'p)
XotiraKesh indekslar bilan bo'lishiladi

Beshta indeksli jadvalda har INSERT oltita yozuv amalini bajaradi (jadval + 5 indeks).

Shuning uchun:

  • Kerak bo'lmagan indeksni o'chiring;
  • Ishlatilmayotgan indekslarni toping;
  • Ko'p yoziladigan jadvalda indeksni minimal tuting.

Ishlatilmayotgan indeksni topish (MariaDB da performance_schema yoqilgan bo'lsa):

SQL
SELECT object_name, index_name, count_star
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL AND count_star = 0;

Takroriy indekslarga ham e'tibor bering:

SQL
INDEX (a)
INDEX (a, b)      -- birinchisi ORTIQCHA, chapdan prefiks qoidasi

(a, b) indeksi (a) ning ishini ham bajaradi.

Qaysi ustunga indeks kerak #

Indeks qo'yish qoidalari
Qo'yingSabab
WHERE da tez-tez ishlatiladigan ustunAsosiy maqsad
Tashqi kalitJOIN va cheklov tekshiruvi
ORDER BY / GROUP BY ustuniSaralash bepul bo'ladi
UNIQUE bo'lishi kerak ustunCheklov + indeks
Qo'ymangSabab
Kam selektiv ustunjins, faolmi - foydasi kam
Juda kichik jadvalTo'liq skanerlash tezroq
Ko'p yoziladigan, kam o'qiladigan ustunNarxi foydasidan ko'p
Allaqachon prefiks bilan qamralganTakrorlanish

Selektivlik - noyob qiymatlar ulushi:

SQL
SELECT COUNT(DISTINCT bolim) / COUNT(*) AS selektivlik FROM xodimlar;

Natija 1 ga yaqin bo'lsa - indeks juda foydali. 0 ga yaqin bo'lsa (masalan 0.0002) - indeks deyarli yordam bermaydi, chunki har qiymatda minglab qator bor.

Odatiy chegara: selektivlik 0.01 dan past bo'lsa, indeks ko'pincha ishlatilmaydi.

Selektivlikni o'lchash #

SQL
SELECT
    ROUND(COUNT(DISTINCT bolim)   / COUNT(*), 6) AS bolim,
    ROUND(COUNT(DISTINCT shahar)  / COUNT(*), 6) AS shahar,
    ROUND(COUNT(DISTINCT maosh)   / COUNT(*), 6) AS maosh,
    ROUND(COUNT(DISTINCT familiya)/ COUNT(*), 6) AS familiya
FROM xodimlar;
Natija
+----------+----------+----------+----------+
| bolim    | shahar   | maosh    | familiya |
+----------+----------+----------+----------+
| 0.000200 | 0.000250 | 0.002500 | 1.000000 |
+----------+----------+----------+----------+

familiya selektivligi 1.0 - har qiymat noyob, indeks juda samarali. bolim esa 0.0002 - atigi 4 xil qiymat, indeksdan foyda kam.

Amaliy topshiriq
  1. 20 000 qatorli jadval yarating.
  2. Indekssiz qidiruvda Handler_read_rnd_next ni o'lchang.
  3. Indeks qo'shib, xuddi shu o'lchovni takrorlang.
  4. B+ daraxt nima uchun tez ekanini tushuntiring.
  5. Klasterlangan indeks nima ekanini ayting.
  6. Kompozit indeks yaratib, uch xil so'rovni sinang.
  7. Chapdan prefiks qoidasini o'z so'zingiz bilan tushuntiring.
  8. Using index chiqadigan so'rov yozing.
  9. Indeksning to'rtta narxini sanang.
  10. Ustunlar selektivligini hisoblab, qaysiga indeks kerakligini ayting.

Xulosa #

  • Indeks - B+ daraxt, milliard qatorda ham 5 ta o'qish.
  • Handler_read_rnd_next to'liq skanerlashni o'lchaydi.
  • InnoDB da birlamchi kalit - klasterlangan, qatorning o'zi.
  • Ikkilamchi indeks birlamchi kalitni saqlaydi.
  • Uzun birlamchi kalit barcha indeksni kattalashtiradi.
  • Kompozit indeksda chapdan prefiks qoidasi ishlaydi.
  • (a, b) indeksi WHERE b = ? ga yordam bermaydi.
  • Using index - qamrovchi indeks, eng yaxshi belgi.
  • Using filesort va Using temporary - yomon belgilar.
  • Har indeks yozishni sekinlashtiradi va joy egallaydi.
  • Selektivlik 0.01 dan past bo'lsa, indeks kam foyda beradi.
  • (a, b) bor bo'lsa, (a) indeksi ortiqcha.

Keyingi bo'limda EXPLAIN bilan so'rovlarni tahlil qilishni o'rganamiz.

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.