Към съдържанието
wannacode

EXPLAIN: планът на заявката

Накратко: EXPLAIN показва плана на заявката, тоест как базата ще я изпълни: дали ще прочете цялата таблица, или ще търси по индекс (в SQLite EXPLAIN QUERY PLAN пише SCAN или SEARCH).

Проблем

„Справката е бавна“

Колега казва, че справката за посещенията на един клиент е бавна. Може да гадаеш: дали таблицата е голяма, дали условието е сложно, дали липсва индекс. Може и да питаш базата как точно смята да я изпълни.

Всяка база първо прави план на заявката (query plan): от коя таблица да започне, дали да чете всичко или да търси в индекс, дали да сортира. Едва после го изпълнява. Планът може да се види.

Идеята

EXPLAIN QUERY PLAN

В SQLite пишеш EXPLAIN QUERY PLAN пред заявката. Тя не се изпълнява. Вместо резултат получаваш плана:

SQLбаза: магазин

Важната колона е detail. id и parent подреждат стъпките в дърво, когато са повече, а notused не се ползва. Тук може да не им обръщаш внимание. SCAN visits значи: прочети цялата таблица ред по ред.

В това поле индексът по customer_id вече е направен:

SQLбаза: магазин

SEARCH значи търсене по индекс. В скобите е какво търси: customer_id=?, тоест една стойност на customer_id. SCAN или SEARCH е първото, което гледаш в плана.

Пробвай

„Моите поръчки“

Страницата „Моите поръчки“ търси поръчките на клиент по customer_id. Виж плана в полето, после направи индекс idx_orders_customer, така че планът на същата заявка да стане SEARCH.

SQLбаза: магазин

Идеята

Покриващ индекс

Сравни плана на SELECT * по-горе с плана на COUNT(*) със същото условие:

SQLбаза: магазин

Тук пише USING COVERING INDEX. Индексът пази стойността на customer_id и номера на реда. За COUNT(*) това стига: базата брои записите в индекса и не отваря таблицата. Това се казва покриващ индекс (covering index).

За SELECT * трябват и product_id, и seen_at. Те ги няма в индекса, затова за всеки намерен запис базата отива и в таблицата. Номерът на реда (id) е във всеки запис на индекса, затова SELECT id със същото условие е покриваща заявка:

SQLбаза: магазин

Виж го

Скритото сортиране

Страницата показва посещенията на клиента от най-новото. Пусни плана:

SQLбаза: магазин

Вторият ред е USE TEMP B-TREE FOR ORDER BY. Индексът е помогнал да се намерят редовете, но те не са подредени по seen_at. Затова базата ги сортира сама, във временна структура. За 60 реда това е нищо. За 60 000 вече се усеща. В следващия урок ще направим индекс, който дава редовете вече подредени.

Търсенето по първичен ключ има свой запис в плана:

SQLбаза: магазин

Идеята

Как да разбереш защо е бавна

Пусни плана на бавната заявка и потърси:

  • SCAN на голяма таблица, а условието избира малко редове. Липсва индекс или условието не може да го ползва (за това е последният урок от модула).
  • USE TEMP B-TREE. Базата сортира или групира сама. Индекс в правилния ред може да го спести.
  • SEARCH, но пак бавно. Условието избира много редове, или индексът не покрива заявката и за всеки ред се чете таблицата.

Планът казва какво ще стане. Колко време отнема, виждаш, когато пуснеш самата заявка.

По-точно, за любопитните

В SQLite EXPLAIN без QUERY PLAN показва програмата, в която SQLite превежда заявката (команди за неговата виртуална машина). Тя е за хората, които пишат самия SQLite.

В PostgreSQL EXPLAIN показва плана с оценки: колко реда очаква всяка стъпка и колко ще „струва“. EXPLAIN ANALYZE изпълнява заявката наистина и добавя истинското време и истинския брой редове на всяка стъпка. Когато оценката и истината се разминават много, оттам обикновено идва бавният план. Там пълното четене се казва Seq Scan, търсенето по индекс Index Scan, а покриващият индекс Index Only Scan. Внимавай: EXPLAIN ANALYZE на UPDATE или DELETE наистина променя данните, затова го пускай в транзакция и после ROLLBACK.

MySQL има EXPLAIN, а от версия 8.0.18 и EXPLAIN ANALYZE.

Упражнение

Две задачи

1. Отчетът за октомври брои посещенията от 1 октомври 2026 г. нататък. Направи индекс idx_visits_seen, така че планът да е SEARCH.

Индекс, с който планът на отчета за октомври става SEARCH:

SQLбаза: магазин

2. Клиент 4321 иска данните му да бъдат изтрити. Програмата първо трябва само номерата на посещенията му. Индексът по customer_id вече е направен. Напиши заявката така, че базата да не чете таблицата.

Номерата (id) на посещенията на клиент 4321, само от индекса:

SQLбаза: магазин

Тест

Провери се

1. Планът казва SCAN visits. Какво значи това?
2. Какво значи COVERING INDEX в плана?
3. В плана има ред USE TEMP B-TREE FOR ORDER BY. Какво прави базата?
4. EXPLAIN QUERY PLAN SELECT … изпълнява ли заявката?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·