Онлайн час в Google Meet
Лого на 91. НЕГ „Проф. Константин Гълъбов“

12. клас · Избираем модул · Урок 6

Индексиране и оптимизация на заявки

Индексът като подредено копие на колона с указатели към редовете: кога ускорява WHERE, JOIN и ORDER BY, кога само пречи, и как EXPLAIN показва дали се ползва.

Защо заявката е бавна

Върху учебната база с 9 ученици всяка заявка е мигновена и не личи дали е написана добре. Реалните бази са други: дневникът на цяло училище за десет години, продажбите на верига магазини, логовете на един сайт — милиони редове. Там заявка, която „работи“, може да отнема минути, а същата заявка с един добавен индекс — милисекунди.

Помислете как търсите дума в речник. Не четете от първата страница — отваряте по средата, гледате буквата, отваряте пак по средата на нужната половина. При 1000 страници стигате за около 10 отваряния. Речникът може така, защото е подреден. Таблицата в базата не е — редовете стоят по реда на вмъкване. За да намери WHERE grad = 'Варна', СУБД трябва да прочете всеки ред. Това се нарича пълно сканиране (full scan).

Какво е индекс

Индекс

Отделна структура, която пази стойностите на една или няколко колони подредени, а до всяка стойност — указател към реда в таблицата. Като азбучника в края на учебник: подреден списък от думи с номера на страниците.

индекс (grad)таблица prodazhbiБургас→4Варна→2Варна→5Пловдив→6Русе→3София→11София372Варна743Русе1114Бургас1485Варна1856Пловдив222подредено — търси се с делене наполовинапо ред на вмъкване — иначе се чете цялата
Индексът е подредено копие на колоната с указатели. Таблицата остава каквато е.

Реалният индекс не е плосък списък, а Б-дърво (B-tree): на всяко ниво стойността се сравнява с няколко граници и търсенето продължава само в един клон. При милион реда листата се достигат за 3–4 стъпки. Дървото се преподрежда само при всяко вмъкване и изтриване — това е и цената му.

≤ Варна | > Варна≤ Бургас | > Бургас≤ Пловдив | > ПловдивБургасВарна · ВарнаПловдивРусе · София
Б-дърво: всяко ниво отсича по-голямата част от редовете, докато се стигне до листото със стойността.
-- обикновен индекс по една колона
CREATE INDEX idx_prodazhbi_grad ON prodazhbi(grad);

-- съставен индекс: първо по клас, после по успех
CREATE INDEX idx_students_class_grade ON students(class_id, grade);

-- уникален индекс: и ускорява, и забранява повторения
CREATE UNIQUE INDEX ux_potrebiteli_email ON potrebiteli(email);

-- премахване
DROP INDEX idx_prodazhbi_grad;                 -- SQLite
DROP INDEX idx_prodazhbi_grad ON prodazhbi;    -- MySQL

Кога помага и кога пречи

Помага

  • WHERE grad = 'Варна', WHERE suma BETWEEN 100 AND 200, WHERE ime LIKE 'Ив%' — търсене и диапазон по индексираната колона.
  • JOIN … ON m.student_id = s.id — колоните на съединението, особено външните ключове.
  • ORDER BY grad — редовете вече са подредени, не се сортират наново.
  • Колоната е селективна: стойността отсича повечето редове. Индекс по ЕГН отсича всичко освен един ред; индекс по колона „пол“ отсича половината — почти без полза.

Пречи или не помага

  • Малки таблици — пълното сканиране на 9 реда е по-бързо от обхождане на дърво.
  • Таблици с много INSERT/UPDATE/DELETE — всеки запис обновява и всеки индекс. Пет индекса = шест записа.
  • Функция върху колоната: WHERE UPPER(grad) = 'ВАРНА' или WHERE YEAR(data) = 2026 — индексът пази grad, не UPPER(grad), и не се ползва.
  • LIKE '%ов' — търсене отзад; азбучникът е подреден отпред.
  • Заема място на диска — при големи таблици колкото самата таблица.
С индексБез индекс
Търсене по колонатаБ-дърво, няколко стъпкиПълно сканиране на всеки ред
Вмъкване и промянаПо-бавно — дървото се обновяваПо-бързо
МястоОтделна структура на дискаНищо допълнително
Резултат от заявкатаСъщиятСъщият

Как да видим дали индексът се ползва

Не гадаем — питаме СУБД за плана на изпълнение. В MySQL пред заявката се пише EXPLAIN, в SQLite — EXPLAIN QUERY PLAN. Резултатът не е данните, а описание как ще се стигне до тях.

-- SQLite (в приложението): добавете пред SELECT
EXPLAIN QUERY PLAN
SELECT grad, COUNT(*) FROM prodazhbi WHERE grad = 'Варна';
-- без индекс:  SCAN prodazhbi
-- с индекс:    SEARCH prodazhbi USING INDEX idx_prodazhbi_grad (grad=?)

-- MySQL (в phpMyAdmin):
EXPLAIN SELECT grad, COUNT(*) FROM prodazhbi WHERE grad = 'Варна';
-- гледайте колоните type, key и rows:
--   type = ALL, key = NULL, rows = 200000   -> пълно сканиране
--   type = ref, key = idx_prodazhbi_grad, rows = 40000 -> индексът работи

Какво трябва да запомниш

  • Индекс = подредено копие на колона с указатели към редовете; вътрешно е Б-дърво.
  • Помага при WHERE, JOIN и ORDER BY по индексираната колона, когато колоната е селективна.
  • Пречи при малки таблици и при много записи; не се ползва при функция върху колоната и при LIKE '%…'.
  • Съставният индекс работи от първата колона нататък — редът на колоните има значение.
  • Индексът не променя резултата на заявката, само времето — EXPLAIN показва дали се ползва.
  • Първичният ключ винаги е индексиран; уникалният индекс освен това забранява повторения.